├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── buffer.go ├── debug.go ├── doc ├── .gitignore ├── README.md ├── mount-linux-error-init.seq ├── mount-linux-error-init.seq.png ├── mount-linux.seq ├── mount-linux.seq.png ├── mount-osx-error-init.seq ├── mount-osx-error-init.seq.png ├── mount-osx.seq ├── mount-osx.seq.png ├── mount-sequence.md └── writing-docs.md ├── error_darwin.go ├── error_freebsd.go ├── error_linux.go ├── error_std.go ├── examples ├── clockfs │ └── clockfs.go └── hellofs │ └── hello.go ├── fs ├── bench │ ├── bench_create_test.go │ ├── bench_lookup_test.go │ ├── bench_readwrite_test.go │ └── doc.go ├── fstestutil │ ├── checkdir.go │ ├── debug.go │ ├── doc.go │ ├── mounted.go │ ├── mountinfo.go │ ├── mountinfo_darwin.go │ ├── mountinfo_freebsd.go │ ├── mountinfo_linux.go │ ├── record │ │ ├── buffer.go │ │ ├── record.go │ │ └── wait.go │ └── testfs.go ├── helpers_test.go ├── serve.go ├── serve_darwin_test.go ├── serve_test.go └── tree.go ├── fuse.go ├── fuse_darwin.go ├── fuse_freebsd.go ├── fuse_kernel.go ├── fuse_kernel_darwin.go ├── fuse_kernel_freebsd.go ├── fuse_kernel_linux.go ├── fuse_kernel_std.go ├── fuse_kernel_test.go ├── fuse_linux.go ├── fuseutil └── fuseutil.go ├── go.mod ├── go.sum ├── mount.go ├── mount_darwin.go ├── mount_freebsd.go ├── mount_linux.go ├── options.go ├── options_daemon_timeout_test.go ├── options_darwin.go ├── options_freebsd.go ├── options_helper_test.go ├── options_linux.go ├── options_nocomma_test.go ├── options_test.go ├── protocol.go ├── syscallx ├── doc.go ├── generate ├── msync.go ├── msync_386.go ├── msync_amd64.go ├── syscallx.go ├── syscallx_std.go ├── xattr_darwin.go ├── xattr_darwin_386.go └── xattr_darwin_amd64.go ├── unmount.go ├── unmount_linux.go └── unmount_std.go /.gitattributes: -------------------------------------------------------------------------------- 1 | *.go filter=gofmt 2 | *.cgo filter=gofmt 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .#* 3 | ## the next line needs to start with a backslash to avoid looking like 4 | ## a comment 5 | \#*# 6 | .*.swp 7 | 8 | *.test 9 | 10 | /clockfs 11 | /hellofs 12 | 13 | *.iml 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2015 Tommi Virtanen. 2 | Copyright (c) 2009, 2011, 2012 The Go Authors. 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are 7 | met: 8 | 9 | * Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following disclaimer 13 | in the documentation and/or other materials provided with the 14 | distribution. 15 | * Neither the name of Google Inc. nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | 32 | 33 | The following included software components have additional copyright 34 | notices and license terms that may differ from the above. 35 | 36 | 37 | File fuse.go: 38 | 39 | // Adapted from Plan 9 from User Space's src/cmd/9pfuse/fuse.c, 40 | // which carries this notice: 41 | // 42 | // The files in this directory are subject to the following license. 43 | // 44 | // The author of this software is Russ Cox. 45 | // 46 | // Copyright (c) 2006 Russ Cox 47 | // 48 | // Permission to use, copy, modify, and distribute this software for any 49 | // purpose without fee is hereby granted, provided that this entire notice 50 | // is included in all copies of any software which is or includes a copy 51 | // or modification of this software and in all copies of the supporting 52 | // documentation for such software. 53 | // 54 | // THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED 55 | // WARRANTY. IN PARTICULAR, THE AUTHOR MAKES NO REPRESENTATION OR WARRANTY 56 | // OF ANY KIND CONCERNING THE MERCHANTABILITY OF THIS SOFTWARE OR ITS 57 | // FITNESS FOR ANY PARTICULAR PURPOSE. 58 | 59 | 60 | File fuse_kernel.go: 61 | 62 | // Derived from FUSE's fuse_kernel.h 63 | /* 64 | This file defines the kernel interface of FUSE 65 | Copyright (C) 2001-2007 Miklos Szeredi 66 | 67 | 68 | This -- and only this -- header file may also be distributed under 69 | the terms of the BSD Licence as follows: 70 | 71 | Copyright (C) 2001-2007 Miklos Szeredi. All rights reserved. 72 | 73 | Redistribution and use in source and binary forms, with or without 74 | modification, are permitted provided that the following conditions 75 | are met: 76 | 1. Redistributions of source code must retain the above copyright 77 | notice, this list of conditions and the following disclaimer. 78 | 2. Redistributions in binary form must reproduce the above copyright 79 | notice, this list of conditions and the following disclaimer in the 80 | documentation and/or other materials provided with the distribution. 81 | 82 | THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND 83 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 84 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 85 | ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE 86 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 87 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 88 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 89 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 90 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 91 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 92 | SUCH DAMAGE. 93 | */ 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | github.com/seaweedfs/fuse -- Filesystems in Go 2 | =================================== 3 | 4 | This is a fork of http://bazil.org/fuse , for performance 5 | and to merge all good pull requests. 6 | 7 | `github.com/seaweedfs/fuse` is a Go library for writing FUSE userspace 8 | filesystems. 9 | 10 | It is a from-scratch implementation of the kernel-userspace 11 | communication protocol, and does not use the C library from the 12 | project called FUSE. `github.com/seaweedfs/fuse` embraces Go fully for safety and 13 | ease of programming. 14 | 15 | Here’s how to get going: 16 | 17 | go get github.com/seaweedfs/fuse 18 | 19 | Website: http://github.com/seaweedfs/fuse/ 20 | 21 | Github repository: https://github.com/bazil/fuse 22 | 23 | API docs: http://godoc.org/github.com/seaweedfs/fuse 24 | 25 | Our thanks to Russ Cox for his fuse library, which this project is 26 | based on. 27 | -------------------------------------------------------------------------------- /buffer.go: -------------------------------------------------------------------------------- 1 | package fuse 2 | 3 | import "unsafe" 4 | 5 | // buffer provides a mechanism for constructing a message from 6 | // multiple segments. 7 | type buffer []byte 8 | 9 | // alloc allocates size bytes and returns a pointer to the new 10 | // segment. 11 | func (w *buffer) alloc(size uintptr) unsafe.Pointer { 12 | s := int(size) 13 | if len(*w)+s > cap(*w) { 14 | old := *w 15 | *w = make([]byte, len(*w), 2*cap(*w)+s) 16 | copy(*w, old) 17 | } 18 | l := len(*w) 19 | *w = (*w)[:l+s] 20 | return unsafe.Pointer(&(*w)[l]) 21 | } 22 | 23 | func newBuffer(extra uintptr) buffer { 24 | const hdrSize = unsafe.Sizeof(outHeader{}) 25 | buf := make(buffer, hdrSize, hdrSize+extra) 26 | return buf 27 | } 28 | -------------------------------------------------------------------------------- /debug.go: -------------------------------------------------------------------------------- 1 | package fuse 2 | 3 | import ( 4 | "runtime" 5 | ) 6 | 7 | func stack() string { 8 | buf := make([]byte, 1024) 9 | return string(buf[:runtime.Stack(buf, false)]) 10 | } 11 | 12 | func nop(msg interface{}) {} 13 | 14 | // Debug is called to output debug messages, including protocol 15 | // traces. The default behavior is to do nothing. 16 | // 17 | // The messages have human-friendly string representations and are 18 | // safe to marshal to JSON. 19 | // 20 | // Implementations must not retain msg. 21 | var Debug func(msg interface{}) = nop 22 | -------------------------------------------------------------------------------- /doc/.gitignore: -------------------------------------------------------------------------------- 1 | /*.seq.svg 2 | 3 | # not ignoring *.seq.png; we want those committed to the repo 4 | # for embedding on Github 5 | -------------------------------------------------------------------------------- /doc/README.md: -------------------------------------------------------------------------------- 1 | # github.com/seaweedfs/fuse documentation 2 | 3 | See also API docs at http://godoc.org/github.com/seaweedfs/fuse 4 | 5 | - [The mount sequence](mount-sequence.md) 6 | - [Writing documentation](writing-docs.md) 7 | -------------------------------------------------------------------------------- /doc/mount-linux-error-init.seq: -------------------------------------------------------------------------------- 1 | seqdiag { 2 | app; 3 | fuse [label="github.com/seaweedfs/fuse"]; 4 | fusermount; 5 | kernel; 6 | mounts; 7 | 8 | app; 9 | fuse [label="github.com/seaweedfs/fuse"]; 10 | fusermount; 11 | kernel; 12 | mounts; 13 | 14 | app -> fuse [label="Mount"]; 15 | fuse -> fusermount [label="spawn, pass socketpair fd"]; 16 | fusermount -> kernel [label="open /dev/fuse"]; 17 | fusermount -> kernel [label="mount(2)"]; 18 | kernel ->> mounts [label="mount is visible"]; 19 | fusermount <-- kernel [label="mount(2) returns"]; 20 | fuse <<-- fusermount [diagonal, label="exit, receive /dev/fuse fd", leftnote="on Linux, successful exit here\nmeans the mount has happened,\nthough InitRequest might not have yet"]; 21 | app <-- fuse [label="Mount returns\nConn.Ready is already closed"]; 22 | 23 | app -> fuse [label="fs.Serve"]; 24 | fuse => kernel [label="read /dev/fuse fd", note="starts with InitRequest"]; 25 | fuse -> app [label="Init"]; 26 | fuse <-- app [color=red]; 27 | fuse -> kernel [label="write /dev/fuse fd", color=red]; 28 | kernel -> kernel [label="set connection\nstate to error", color=red]; 29 | fuse <-- kernel; 30 | ... conn.MountError == nil, so it is still mounted ... 31 | ... call conn.Close to clean up ... 32 | } 33 | -------------------------------------------------------------------------------- /doc/mount-linux-error-init.seq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaweedfs/fuse/4f81356da6b4bc8a8a896452ce025e81bf635674/doc/mount-linux-error-init.seq.png -------------------------------------------------------------------------------- /doc/mount-linux.seq: -------------------------------------------------------------------------------- 1 | seqdiag { 2 | // seqdiag -T svg -o doc/mount-osx.svg doc/mount-osx.seq 3 | app; 4 | fuse [label="github.com/seaweedfs/fuse"]; 5 | fusermount; 6 | kernel; 7 | mounts; 8 | 9 | app -> fuse [label="Mount"]; 10 | fuse -> fusermount [label="spawn, pass socketpair fd"]; 11 | fusermount -> kernel [label="open /dev/fuse"]; 12 | fusermount -> kernel [label="mount(2)"]; 13 | kernel ->> mounts [label="mount is visible"]; 14 | fusermount <-- kernel [label="mount(2) returns"]; 15 | fuse <<-- fusermount [diagonal, label="exit, receive /dev/fuse fd", leftnote="on Linux, successful exit here\nmeans the mount has happened,\nthough InitRequest might not have yet"]; 16 | app <-- fuse [label="Mount returns\nConn.Ready is already closed", rightnote="InitRequest and StatfsRequest\nmay or may not be seen\nbefore Conn.Ready,\ndepending on platform"]; 17 | 18 | app -> fuse [label="fs.Serve"]; 19 | fuse => kernel [label="read /dev/fuse fd", note="starts with InitRequest"]; 20 | fuse => app [label="FS/Node/Handle methods"]; 21 | fuse => kernel [label="write /dev/fuse fd"]; 22 | ... repeat ... 23 | 24 | ... shutting down ... 25 | app -> fuse [label="Unmount"]; 26 | fuse -> fusermount [label="fusermount -u"]; 27 | fusermount -> kernel; 28 | kernel <<-- mounts; 29 | fusermount <-- kernel; 30 | fuse <<-- fusermount [diagonal]; 31 | app <-- fuse [label="Unmount returns"]; 32 | 33 | // actually triggers before above 34 | fuse <<-- kernel [diagonal, label="/dev/fuse EOF"]; 35 | app <-- fuse [label="fs.Serve returns"]; 36 | 37 | app -> fuse [label="conn.Close"]; 38 | fuse -> kernel [label="close /dev/fuse fd"]; 39 | fuse <-- kernel; 40 | app <-- fuse; 41 | } 42 | -------------------------------------------------------------------------------- /doc/mount-linux.seq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaweedfs/fuse/4f81356da6b4bc8a8a896452ce025e81bf635674/doc/mount-linux.seq.png -------------------------------------------------------------------------------- /doc/mount-osx-error-init.seq: -------------------------------------------------------------------------------- 1 | seqdiag { 2 | app; 3 | fuse [label="github.com/seaweedfs/fuse"]; 4 | wait [label="callMount\nhelper goroutine"]; 5 | mount_osxfusefs; 6 | kernel; 7 | 8 | app -> fuse [label="Mount"]; 9 | fuse -> kernel [label="open /dev/osxfuseN"]; 10 | fuse -> mount_osxfusefs [label="spawn, pass fd"]; 11 | fuse -> wait [label="goroutine", note="blocks on cmd.Wait"]; 12 | app <-- fuse [label="Mount returns"]; 13 | 14 | mount_osxfusefs -> kernel [label="mount(2)"]; 15 | 16 | app -> fuse [label="fs.Serve"]; 17 | fuse => kernel [label="read /dev/osxfuseN fd", note="starts with InitRequest,\nalso seen before mount exits:\ntwo StatfsRequest calls"]; 18 | fuse -> app [label="Init"]; 19 | fuse <-- app [color=red]; 20 | fuse -> kernel [label="write /dev/osxfuseN fd", color=red]; 21 | fuse <-- kernel; 22 | 23 | mount_osxfusefs <-- kernel [label="mount(2) returns", color=red]; 24 | wait <<-- mount_osxfusefs [diagonal, label="exit", color=red]; 25 | app <<-- wait [diagonal, label="mount has failed,\nclose Conn.Ready", color=red]; 26 | 27 | // actually triggers before above 28 | fuse <<-- kernel [diagonal, label="/dev/osxfuseN EOF"]; 29 | app <-- fuse [label="fs.Serve returns"]; 30 | ... conn.MountError != nil, so it was was never mounted ... 31 | ... call conn.Close to clean up ... 32 | } 33 | -------------------------------------------------------------------------------- /doc/mount-osx-error-init.seq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaweedfs/fuse/4f81356da6b4bc8a8a896452ce025e81bf635674/doc/mount-osx-error-init.seq.png -------------------------------------------------------------------------------- /doc/mount-osx.seq: -------------------------------------------------------------------------------- 1 | seqdiag { 2 | // seqdiag -T svg -o doc/mount-osx.svg doc/mount-osx.seq 3 | app; 4 | fuse [label="github.com/seaweedfs/fuse"]; 5 | wait [label="callMount\nhelper goroutine"]; 6 | mount_osxfusefs; 7 | kernel; 8 | mounts; 9 | 10 | app -> fuse [label="Mount"]; 11 | fuse -> kernel [label="open /dev/osxfuseN"]; 12 | fuse -> mount_osxfusefs [label="spawn, pass fd"]; 13 | fuse -> wait [label="goroutine", note="blocks on cmd.Wait"]; 14 | app <-- fuse [label="Mount returns"]; 15 | 16 | mount_osxfusefs -> kernel [label="mount(2)"]; 17 | 18 | app -> fuse [label="fs.Serve"]; 19 | fuse => kernel [label="read /dev/osxfuseN fd", note="starts with InitRequest,\nalso seen before mount exits:\ntwo StatfsRequest calls"]; 20 | fuse => app [label="FS/Node/Handle methods"]; 21 | fuse => kernel [label="write /dev/osxfuseN fd"]; 22 | ... repeat ... 23 | 24 | kernel ->> mounts [label="mount is visible"]; 25 | mount_osxfusefs <-- kernel [label="mount(2) returns"]; 26 | wait <<-- mount_osxfusefs [diagonal, label="exit", leftnote="on OS X, successful exit\nhere means we finally know\nthe mount has happened\n(can't trust InitRequest,\nkernel might have timed out\nwaiting for InitResponse)"]; 27 | 28 | app <<-- wait [diagonal, label="mount is ready,\nclose Conn.Ready", rightnote="InitRequest and StatfsRequest\nmay or may not be seen\nbefore Conn.Ready,\ndepending on platform"]; 29 | 30 | ... shutting down ... 31 | app -> fuse [label="Unmount"]; 32 | fuse -> kernel [label="umount(2)"]; 33 | kernel <<-- mounts; 34 | fuse <-- kernel; 35 | app <-- fuse [label="Unmount returns"]; 36 | 37 | // actually triggers before above 38 | fuse <<-- kernel [diagonal, label="/dev/osxfuseN EOF"]; 39 | app <-- fuse [label="fs.Serve returns"]; 40 | 41 | app -> fuse [label="conn.Close"]; 42 | fuse -> kernel [label="close /dev/osxfuseN"]; 43 | fuse <-- kernel; 44 | app <-- fuse; 45 | } 46 | -------------------------------------------------------------------------------- /doc/mount-osx.seq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seaweedfs/fuse/4f81356da6b4bc8a8a896452ce025e81bf635674/doc/mount-osx.seq.png -------------------------------------------------------------------------------- /doc/mount-sequence.md: -------------------------------------------------------------------------------- 1 | # The mount sequence 2 | 3 | FUSE mounting is a little bit tricky. There's a userspace helper tool 4 | that performs the handshake with the kernel, and then steps out of the 5 | way. This helper behaves differently on different platforms, forcing a 6 | more complex API on us. 7 | 8 | ## Successful runs 9 | 10 | On Linux, the mount is immediate and file system accesses wait until 11 | the requests are served. 12 | 13 | ![Diagram of Linux FUSE mount sequence](mount-linux.seq.png) 14 | 15 | On OS X, the mount becomes visible only after `InitRequest` (and maybe 16 | more) have been served. 17 | 18 | ![Diagram of OSXFUSE mount sequence](mount-osx.seq.png) 19 | 20 | 21 | ## Errors 22 | 23 | Let's see what happens if `InitRequest` gets an error response. On 24 | Linux, the mountpoint is there but all operations will fail: 25 | 26 | ![Diagram of Linux error handling](mount-linux-error-init.seq.png) 27 | 28 | On OS X, the mount never happened: 29 | 30 | ![Diagram of OS X error handling](mount-osx-error-init.seq.png) 31 | -------------------------------------------------------------------------------- /doc/writing-docs.md: -------------------------------------------------------------------------------- 1 | # Writing documentation 2 | 3 | ## Sequence diagrams 4 | 5 | The sequence diagrams are generated with `seqdiag`: 6 | http://blockdiag.com/en/seqdiag/index.html 7 | 8 | An easy way to work on them is to automatically update the generated 9 | files with https://github.com/cespare/reflex : 10 | 11 | reflex -g 'doc/[^.]*.seq' -- seqdiag -T svg -o '{}.svg' '{}' & 12 | 13 | reflex -g 'doc/[^.]*.seq' -- seqdiag -T png -o '{}.png' '{}' & 14 | 15 | The markdown files refer to PNG images because of Github limitations, 16 | but the SVG is generally more pleasant to view. 17 | -------------------------------------------------------------------------------- /error_darwin.go: -------------------------------------------------------------------------------- 1 | package fuse 2 | 3 | import ( 4 | "syscall" 5 | ) 6 | 7 | const ( 8 | ENOATTR = Errno(syscall.ENOATTR) 9 | ) 10 | 11 | const ( 12 | errNoXattr = ENOATTR 13 | ) 14 | 15 | func init() { 16 | errnoNames[errNoXattr] = "ENOATTR" 17 | } 18 | -------------------------------------------------------------------------------- /error_freebsd.go: -------------------------------------------------------------------------------- 1 | package fuse 2 | 3 | import "syscall" 4 | 5 | const ( 6 | ENOATTR = Errno(syscall.ENOATTR) 7 | ) 8 | 9 | const ( 10 | errNoXattr = ENOATTR 11 | ) 12 | 13 | func init() { 14 | errnoNames[errNoXattr] = "ENOATTR" 15 | } 16 | -------------------------------------------------------------------------------- /error_linux.go: -------------------------------------------------------------------------------- 1 | package fuse 2 | 3 | import ( 4 | "syscall" 5 | ) 6 | 7 | const ( 8 | ENODATA = Errno(syscall.ENODATA) 9 | ) 10 | 11 | const ( 12 | errNoXattr = ENODATA 13 | ) 14 | 15 | func init() { 16 | errnoNames[errNoXattr] = "ENODATA" 17 | } 18 | -------------------------------------------------------------------------------- /error_std.go: -------------------------------------------------------------------------------- 1 | package fuse 2 | 3 | // There is very little commonality in extended attribute errors 4 | // across platforms. 5 | // 6 | // getxattr return value for "extended attribute does not exist" is 7 | // ENOATTR on OS X, and ENODATA on Linux and apparently at least 8 | // NetBSD. There may be a #define ENOATTR on Linux too, but the value 9 | // is ENODATA in the actual syscalls. FreeBSD and OpenBSD have no 10 | // ENODATA, only ENOATTR. ENOATTR is not in any of the standards, 11 | // ENODATA exists but is only used for STREAMs. 12 | // 13 | // Each platform will define it a errNoXattr constant, and this file 14 | // will enforce that it implements the right interfaces and hide the 15 | // implementation. 16 | // 17 | // https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/getxattr.2.html 18 | // http://mail-index.netbsd.org/tech-kern/2012/04/30/msg013090.html 19 | // http://mail-index.netbsd.org/tech-kern/2012/04/30/msg013097.html 20 | // http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/errno.h.html 21 | // http://www.freebsd.org/cgi/man.cgi?query=extattr_get_file&sektion=2 22 | // http://nixdoc.net/man-pages/openbsd/man2/extattr_get_file.2.html 23 | 24 | // ErrNoXattr is a platform-independent error value meaning the 25 | // extended attribute was not found. It can be used to respond to 26 | // GetxattrRequest and such. 27 | const ErrNoXattr = errNoXattr 28 | 29 | var _ error = ErrNoXattr 30 | var _ Errno = ErrNoXattr 31 | var _ ErrorNumber = ErrNoXattr 32 | -------------------------------------------------------------------------------- /examples/clockfs/clockfs.go: -------------------------------------------------------------------------------- 1 | // Clockfs implements a file system with the current time in a file. 2 | // It was written to demonstrate kernel cache invalidation. 3 | package main 4 | 5 | import ( 6 | "context" 7 | "flag" 8 | "fmt" 9 | "log" 10 | "os" 11 | "sync/atomic" 12 | "syscall" 13 | "time" 14 | 15 | "github.com/seaweedfs/fuse" 16 | "github.com/seaweedfs/fuse/fs" 17 | _ "github.com/seaweedfs/fuse/fs/fstestutil" 18 | "github.com/seaweedfs/fuse/fuseutil" 19 | ) 20 | 21 | func usage() { 22 | fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) 23 | fmt.Fprintf(os.Stderr, " %s MOUNTPOINT\n", os.Args[0]) 24 | flag.PrintDefaults() 25 | } 26 | 27 | func run(mountpoint string) error { 28 | c, err := fuse.Mount( 29 | mountpoint, 30 | fuse.FSName("clock"), 31 | fuse.Subtype("clockfsfs"), 32 | fuse.LocalVolume(), 33 | fuse.VolumeName("Clock filesystem"), 34 | ) 35 | if err != nil { 36 | return err 37 | } 38 | defer c.Close() 39 | 40 | if p := c.Protocol(); !p.HasInvalidate() { 41 | return fmt.Errorf("kernel FUSE support is too old to have invalidations: version %v", p) 42 | } 43 | 44 | srv := fs.New(c, nil) 45 | filesys := &FS{ 46 | // We pre-create the clock node so that it's always the same 47 | // object returned from all the Lookups. You could carefully 48 | // track its lifetime between Lookup&Forget, and have the 49 | // ticking & invalidation happen only when active, but let's 50 | // keep this example simple. 51 | clockFile: &File{ 52 | fuse: srv, 53 | }, 54 | } 55 | filesys.clockFile.tick() 56 | // This goroutine never exits. That's fine for this example. 57 | go filesys.clockFile.update() 58 | if err := srv.Serve(filesys); err != nil { 59 | return err 60 | } 61 | 62 | // Check if the mount process has an error to report. 63 | <-c.Ready 64 | if err := c.MountError; err != nil { 65 | return err 66 | } 67 | return nil 68 | } 69 | 70 | func main() { 71 | flag.Usage = usage 72 | flag.Parse() 73 | 74 | if flag.NArg() != 1 { 75 | usage() 76 | os.Exit(2) 77 | } 78 | mountpoint := flag.Arg(0) 79 | 80 | if err := run(mountpoint); err != nil { 81 | log.Fatal(err) 82 | } 83 | } 84 | 85 | type FS struct { 86 | clockFile *File 87 | } 88 | 89 | var _ fs.FS = (*FS)(nil) 90 | 91 | func (f *FS) Root() (fs.Node, error) { 92 | return &Dir{fs: f}, nil 93 | } 94 | 95 | // Dir implements both Node and Handle for the root directory. 96 | type Dir struct { 97 | fs *FS 98 | } 99 | 100 | var _ fs.Node = (*Dir)(nil) 101 | 102 | func (d *Dir) Attr(ctx context.Context, a *fuse.Attr) error { 103 | a.Inode = 1 104 | a.Mode = os.ModeDir | 0555 105 | return nil 106 | } 107 | 108 | var _ fs.NodeStringLookuper = (*Dir)(nil) 109 | 110 | func (d *Dir) Lookup(ctx context.Context, name string) (fs.Node, error) { 111 | if name == "clock" { 112 | return d.fs.clockFile, nil 113 | } 114 | return nil, fuse.ENOENT 115 | } 116 | 117 | var dirDirs = []fuse.Dirent{ 118 | {Inode: 2, Name: "clock", Type: fuse.DT_File}, 119 | } 120 | 121 | var _ fs.HandleReadDirAller = (*Dir)(nil) 122 | 123 | func (d *Dir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { 124 | return dirDirs, nil 125 | } 126 | 127 | type File struct { 128 | fs.NodeRef 129 | fuse *fs.Server 130 | content atomic.Value 131 | count uint64 132 | } 133 | 134 | var _ fs.Node = (*File)(nil) 135 | 136 | func (f *File) Attr(ctx context.Context, a *fuse.Attr) error { 137 | a.Inode = 2 138 | a.Mode = 0444 139 | t := f.content.Load().(string) 140 | a.Size = uint64(len(t)) 141 | return nil 142 | } 143 | 144 | var _ fs.NodeOpener = (*File)(nil) 145 | 146 | func (f *File) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) { 147 | if !req.Flags.IsReadOnly() { 148 | return nil, fuse.Errno(syscall.EACCES) 149 | } 150 | resp.Flags |= fuse.OpenKeepCache 151 | return f, nil 152 | } 153 | 154 | var _ fs.Handle = (*File)(nil) 155 | 156 | var _ fs.HandleReader = (*File)(nil) 157 | 158 | func (f *File) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error { 159 | t := f.content.Load().(string) 160 | fuseutil.HandleRead(req, resp, []byte(t)) 161 | return nil 162 | } 163 | 164 | func (f *File) tick() { 165 | // Intentionally a variable-length format, to demonstrate size changes. 166 | f.count++ 167 | s := fmt.Sprintf("%d\t%s\n", f.count, time.Now()) 168 | f.content.Store(s) 169 | 170 | // For simplicity, this example tries to send invalidate 171 | // notifications even when the kernel does not hold a reference to 172 | // the node, so be extra sure to ignore ErrNotCached. 173 | if err := f.fuse.InvalidateNodeData(f); err != nil && err != fuse.ErrNotCached { 174 | log.Printf("invalidate error: %v", err) 175 | } 176 | } 177 | 178 | func (f *File) update() { 179 | tick := time.NewTicker(1 * time.Second) 180 | defer tick.Stop() 181 | for range tick.C { 182 | f.tick() 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /examples/hellofs/hello.go: -------------------------------------------------------------------------------- 1 | // Hellofs implements a simple "hello world" file system. 2 | package main 3 | 4 | import ( 5 | "context" 6 | "flag" 7 | "fmt" 8 | "log" 9 | "os" 10 | 11 | "github.com/seaweedfs/fuse" 12 | "github.com/seaweedfs/fuse/fs" 13 | _ "github.com/seaweedfs/fuse/fs/fstestutil" 14 | ) 15 | 16 | func usage() { 17 | fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) 18 | fmt.Fprintf(os.Stderr, " %s MOUNTPOINT\n", os.Args[0]) 19 | flag.PrintDefaults() 20 | } 21 | 22 | func main() { 23 | flag.Usage = usage 24 | flag.Parse() 25 | 26 | if flag.NArg() != 1 { 27 | usage() 28 | os.Exit(2) 29 | } 30 | mountpoint := flag.Arg(0) 31 | 32 | c, err := fuse.Mount( 33 | mountpoint, 34 | fuse.FSName("helloworld"), 35 | fuse.Subtype("hellofs"), 36 | fuse.LocalVolume(), 37 | fuse.VolumeName("Hello world!"), 38 | ) 39 | if err != nil { 40 | log.Fatal(err) 41 | } 42 | defer c.Close() 43 | 44 | err = fs.Serve(c, FS{}) 45 | if err != nil { 46 | log.Fatal(err) 47 | } 48 | 49 | // check if the mount process has an error to report 50 | <-c.Ready 51 | if err := c.MountError; err != nil { 52 | log.Fatal(err) 53 | } 54 | } 55 | 56 | // FS implements the hello world file system. 57 | type FS struct{} 58 | 59 | func (FS) Root() (fs.Node, error) { 60 | return Dir{}, nil 61 | } 62 | 63 | // Dir implements both Node and Handle for the root directory. 64 | type Dir struct{} 65 | 66 | func (Dir) Attr(ctx context.Context, a *fuse.Attr) error { 67 | a.Inode = 1 68 | a.Mode = os.ModeDir | 0555 69 | return nil 70 | } 71 | 72 | func (Dir) Lookup(ctx context.Context, name string) (fs.Node, error) { 73 | if name == "hello" { 74 | return File{}, nil 75 | } 76 | return nil, fuse.ENOENT 77 | } 78 | 79 | var dirDirs = []fuse.Dirent{ 80 | {Inode: 2, Name: "hello", Type: fuse.DT_File}, 81 | } 82 | 83 | func (Dir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { 84 | return dirDirs, nil 85 | } 86 | 87 | // File implements both Node and Handle for the hello file. 88 | type File struct{} 89 | 90 | const greeting = "hello, world\n" 91 | 92 | func (File) Attr(ctx context.Context, a *fuse.Attr) error { 93 | a.Inode = 2 94 | a.Mode = 0444 95 | a.Size = uint64(len(greeting)) 96 | return nil 97 | } 98 | 99 | func (File) ReadAll(ctx context.Context) ([]byte, error) { 100 | return []byte(greeting), nil 101 | } 102 | -------------------------------------------------------------------------------- /fs/bench/bench_create_test.go: -------------------------------------------------------------------------------- 1 | package bench_test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "testing" 8 | 9 | "github.com/seaweedfs/fuse" 10 | "github.com/seaweedfs/fuse/fs" 11 | "github.com/seaweedfs/fuse/fs/fstestutil" 12 | ) 13 | 14 | type dummyFile struct { 15 | fstestutil.File 16 | } 17 | 18 | type benchCreateDir struct { 19 | fstestutil.Dir 20 | } 21 | 22 | var _ fs.NodeCreater = (*benchCreateDir)(nil) 23 | 24 | func (f *benchCreateDir) Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (fs.Node, fs.Handle, error) { 25 | child := &dummyFile{} 26 | return child, child, nil 27 | } 28 | 29 | func BenchmarkCreate(b *testing.B) { 30 | f := &benchCreateDir{} 31 | mnt, err := fstestutil.MountedT(b, fstestutil.SimpleFS{f}, nil) 32 | if err != nil { 33 | b.Fatal(err) 34 | } 35 | defer mnt.Close() 36 | 37 | // prepare file names to decrease test overhead 38 | names := make([]string, 0, b.N) 39 | for i := 0; i < b.N; i++ { 40 | // zero-padded so cost stays the same on every iteration 41 | names = append(names, mnt.Dir+"/"+fmt.Sprintf("%08x", i)) 42 | } 43 | b.ResetTimer() 44 | 45 | for i := 0; i < b.N; i++ { 46 | f, err := os.Create(names[i]) 47 | if err != nil { 48 | b.Fatalf("WriteFile: %v", err) 49 | } 50 | f.Close() 51 | } 52 | 53 | b.StopTimer() 54 | } 55 | -------------------------------------------------------------------------------- /fs/bench/bench_lookup_test.go: -------------------------------------------------------------------------------- 1 | package bench_test 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "testing" 7 | 8 | "github.com/seaweedfs/fuse" 9 | "github.com/seaweedfs/fuse/fs" 10 | "github.com/seaweedfs/fuse/fs/fstestutil" 11 | ) 12 | 13 | type benchLookupDir struct { 14 | fstestutil.Dir 15 | } 16 | 17 | var _ fs.NodeRequestLookuper = (*benchLookupDir)(nil) 18 | 19 | func (f *benchLookupDir) Lookup(ctx context.Context, req *fuse.LookupRequest, resp *fuse.LookupResponse) (fs.Node, error) { 20 | return nil, fuse.ENOENT 21 | } 22 | 23 | func BenchmarkLookup(b *testing.B) { 24 | f := &benchLookupDir{} 25 | mnt, err := fstestutil.MountedT(b, fstestutil.SimpleFS{f}, nil) 26 | if err != nil { 27 | b.Fatal(err) 28 | } 29 | defer mnt.Close() 30 | 31 | name := mnt.Dir + "/does-not-exist" 32 | b.ResetTimer() 33 | 34 | for i := 0; i < b.N; i++ { 35 | if _, err := os.Stat(name); !os.IsNotExist(err) { 36 | b.Fatalf("Stat: wrong error: %v", err) 37 | } 38 | } 39 | 40 | b.StopTimer() 41 | } 42 | -------------------------------------------------------------------------------- /fs/bench/bench_readwrite_test.go: -------------------------------------------------------------------------------- 1 | package bench_test 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "io/ioutil" 7 | "os" 8 | "path" 9 | "testing" 10 | 11 | "github.com/seaweedfs/fuse" 12 | "github.com/seaweedfs/fuse/fs" 13 | "github.com/seaweedfs/fuse/fs/fstestutil" 14 | ) 15 | 16 | type benchConfig struct { 17 | directIO bool 18 | } 19 | 20 | type benchFS struct { 21 | conf *benchConfig 22 | } 23 | 24 | var _ = fs.FS(benchFS{}) 25 | 26 | func (f benchFS) Root() (fs.Node, error) { 27 | return benchDir{conf: f.conf}, nil 28 | } 29 | 30 | type benchDir struct { 31 | conf *benchConfig 32 | } 33 | 34 | var _ = fs.Node(benchDir{}) 35 | var _ = fs.NodeStringLookuper(benchDir{}) 36 | var _ = fs.Handle(benchDir{}) 37 | var _ = fs.HandleReadDirAller(benchDir{}) 38 | 39 | func (benchDir) Attr(ctx context.Context, a *fuse.Attr) error { 40 | a.Inode = 1 41 | a.Mode = os.ModeDir | 0555 42 | return nil 43 | } 44 | 45 | func (d benchDir) Lookup(ctx context.Context, name string) (fs.Node, error) { 46 | if name == "bench" { 47 | return benchFile{conf: d.conf}, nil 48 | } 49 | return nil, fuse.ENOENT 50 | } 51 | 52 | func (benchDir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { 53 | l := []fuse.Dirent{ 54 | {Inode: 2, Name: "bench", Type: fuse.DT_File}, 55 | } 56 | return l, nil 57 | } 58 | 59 | type benchFile struct { 60 | conf *benchConfig 61 | } 62 | 63 | var _ = fs.Node(benchFile{}) 64 | var _ = fs.NodeOpener(benchFile{}) 65 | var _ = fs.NodeFsyncer(benchFile{}) 66 | var _ = fs.Handle(benchFile{}) 67 | var _ = fs.HandleReader(benchFile{}) 68 | var _ = fs.HandleWriter(benchFile{}) 69 | 70 | func (benchFile) Attr(ctx context.Context, a *fuse.Attr) error { 71 | a.Inode = 2 72 | a.Mode = 0644 73 | a.Size = 9999999999999999 74 | return nil 75 | } 76 | 77 | func (f benchFile) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) { 78 | if f.conf.directIO { 79 | resp.Flags |= fuse.OpenDirectIO 80 | } 81 | // TODO configurable? 82 | resp.Flags |= fuse.OpenKeepCache 83 | return f, nil 84 | } 85 | 86 | func (benchFile) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error { 87 | resp.Data = resp.Data[:cap(resp.Data)] 88 | return nil 89 | } 90 | 91 | func (benchFile) Write(ctx context.Context, req *fuse.WriteRequest, resp *fuse.WriteResponse) error { 92 | resp.Size = len(req.Data) 93 | return nil 94 | } 95 | 96 | func (benchFile) Fsync(ctx context.Context, req *fuse.FsyncRequest) error { 97 | return nil 98 | } 99 | 100 | func benchmark(b *testing.B, fn func(b *testing.B, mnt string), conf *benchConfig) { 101 | filesys := benchFS{ 102 | conf: conf, 103 | } 104 | mnt, err := fstestutil.Mounted(filesys, nil, 105 | fuse.MaxReadahead(64*1024*1024), 106 | fuse.AsyncRead(), 107 | fuse.WritebackCache(), 108 | ) 109 | if err != nil { 110 | b.Fatal(err) 111 | } 112 | defer mnt.Close() 113 | 114 | fn(b, mnt.Dir) 115 | } 116 | 117 | type zero struct{} 118 | 119 | func (zero) Read(p []byte) (n int, err error) { 120 | return len(p), nil 121 | } 122 | 123 | var Zero io.Reader = zero{} 124 | 125 | func doWrites(size int64) func(b *testing.B, mnt string) { 126 | return func(b *testing.B, mnt string) { 127 | p := path.Join(mnt, "bench") 128 | 129 | f, err := os.Create(p) 130 | if err != nil { 131 | b.Fatalf("create: %v", err) 132 | } 133 | defer f.Close() 134 | 135 | b.ResetTimer() 136 | b.SetBytes(size) 137 | 138 | for i := 0; i < b.N; i++ { 139 | _, err = io.CopyN(f, Zero, size) 140 | if err != nil { 141 | b.Fatalf("write: %v", err) 142 | } 143 | } 144 | } 145 | } 146 | 147 | func BenchmarkWrite100(b *testing.B) { 148 | benchmark(b, doWrites(100), &benchConfig{}) 149 | } 150 | 151 | func BenchmarkWrite10MB(b *testing.B) { 152 | benchmark(b, doWrites(10*1024*1024), &benchConfig{}) 153 | } 154 | 155 | func BenchmarkWrite100MB(b *testing.B) { 156 | benchmark(b, doWrites(100*1024*1024), &benchConfig{}) 157 | } 158 | 159 | func BenchmarkDirectWrite100(b *testing.B) { 160 | benchmark(b, doWrites(100), &benchConfig{ 161 | directIO: true, 162 | }) 163 | } 164 | 165 | func BenchmarkDirectWrite10MB(b *testing.B) { 166 | benchmark(b, doWrites(10*1024*1024), &benchConfig{ 167 | directIO: true, 168 | }) 169 | } 170 | 171 | func BenchmarkDirectWrite100MB(b *testing.B) { 172 | benchmark(b, doWrites(100*1024*1024), &benchConfig{ 173 | directIO: true, 174 | }) 175 | } 176 | 177 | func doWritesSync(size int64) func(b *testing.B, mnt string) { 178 | return func(b *testing.B, mnt string) { 179 | p := path.Join(mnt, "bench") 180 | 181 | f, err := os.Create(p) 182 | if err != nil { 183 | b.Fatalf("create: %v", err) 184 | } 185 | defer f.Close() 186 | 187 | b.ResetTimer() 188 | b.SetBytes(size) 189 | 190 | for i := 0; i < b.N; i++ { 191 | _, err = io.CopyN(f, Zero, size) 192 | if err != nil { 193 | b.Fatalf("write: %v", err) 194 | } 195 | 196 | if err := f.Sync(); err != nil { 197 | b.Fatalf("sync: %v", err) 198 | } 199 | } 200 | } 201 | } 202 | 203 | func BenchmarkWriteSync100(b *testing.B) { 204 | benchmark(b, doWritesSync(100), &benchConfig{}) 205 | } 206 | 207 | func BenchmarkWriteSync10MB(b *testing.B) { 208 | benchmark(b, doWritesSync(10*1024*1024), &benchConfig{}) 209 | } 210 | 211 | func BenchmarkWriteSync100MB(b *testing.B) { 212 | benchmark(b, doWritesSync(100*1024*1024), &benchConfig{}) 213 | } 214 | 215 | func doReads(size int64) func(b *testing.B, mnt string) { 216 | return func(b *testing.B, mnt string) { 217 | p := path.Join(mnt, "bench") 218 | 219 | f, err := os.Open(p) 220 | if err != nil { 221 | b.Fatalf("close: %v", err) 222 | } 223 | defer f.Close() 224 | 225 | b.ResetTimer() 226 | b.SetBytes(size) 227 | 228 | for i := 0; i < b.N; i++ { 229 | n, err := io.CopyN(ioutil.Discard, f, size) 230 | if err != nil { 231 | b.Fatalf("read: %v", err) 232 | } 233 | if n != size { 234 | b.Errorf("unexpected size: %d != %d", n, size) 235 | } 236 | } 237 | } 238 | } 239 | 240 | func BenchmarkRead100(b *testing.B) { 241 | benchmark(b, doReads(100), &benchConfig{}) 242 | } 243 | 244 | func BenchmarkRead10MB(b *testing.B) { 245 | benchmark(b, doReads(10*1024*1024), &benchConfig{}) 246 | } 247 | 248 | func BenchmarkRead100MB(b *testing.B) { 249 | benchmark(b, doReads(100*1024*1024), &benchConfig{}) 250 | } 251 | 252 | func BenchmarkDirectRead100(b *testing.B) { 253 | benchmark(b, doReads(100), &benchConfig{ 254 | directIO: true, 255 | }) 256 | } 257 | 258 | func BenchmarkDirectRead10MB(b *testing.B) { 259 | benchmark(b, doReads(10*1024*1024), &benchConfig{ 260 | directIO: true, 261 | }) 262 | } 263 | 264 | func BenchmarkDirectRead100MB(b *testing.B) { 265 | benchmark(b, doReads(100*1024*1024), &benchConfig{ 266 | directIO: true, 267 | }) 268 | } 269 | -------------------------------------------------------------------------------- /fs/bench/doc.go: -------------------------------------------------------------------------------- 1 | // Package bench contains benchmarks. 2 | // 3 | // It is kept in a separate package to avoid conflicting with the 4 | // debug-heavy defaults for the actual tests. 5 | package bench 6 | -------------------------------------------------------------------------------- /fs/fstestutil/checkdir.go: -------------------------------------------------------------------------------- 1 | package fstestutil 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "os" 7 | ) 8 | 9 | // FileInfoCheck is a function that validates an os.FileInfo according 10 | // to some criteria. 11 | type FileInfoCheck func(fi os.FileInfo) error 12 | 13 | type checkDirError struct { 14 | missing map[string]struct{} 15 | extra map[string]os.FileMode 16 | } 17 | 18 | func (e *checkDirError) Error() string { 19 | return fmt.Sprintf("wrong directory contents: missing %v, extra %v", e.missing, e.extra) 20 | } 21 | 22 | // CheckDir checks the contents of the directory at path, making sure 23 | // every directory entry listed in want is present. If the check is 24 | // not nil, it must also pass. 25 | // 26 | // If want contains the impossible filename "", unexpected files are 27 | // checked with that. If the key is not in want, unexpected files are 28 | // an error. 29 | // 30 | // Missing entries, that are listed in want but not seen, are an 31 | // error. 32 | func CheckDir(path string, want map[string]FileInfoCheck) error { 33 | problems := &checkDirError{ 34 | missing: make(map[string]struct{}, len(want)), 35 | extra: make(map[string]os.FileMode), 36 | } 37 | for k := range want { 38 | if k == "" { 39 | continue 40 | } 41 | problems.missing[k] = struct{}{} 42 | } 43 | 44 | fis, err := ioutil.ReadDir(path) 45 | if err != nil { 46 | return fmt.Errorf("cannot read directory: %v", err) 47 | } 48 | 49 | for _, fi := range fis { 50 | check, ok := want[fi.Name()] 51 | if !ok { 52 | check, ok = want[""] 53 | } 54 | if !ok { 55 | problems.extra[fi.Name()] = fi.Mode() 56 | continue 57 | } 58 | delete(problems.missing, fi.Name()) 59 | if check != nil { 60 | if err := check(fi); err != nil { 61 | return fmt.Errorf("check failed: %v: %v", fi.Name(), err) 62 | } 63 | } 64 | } 65 | 66 | if len(problems.missing) > 0 || len(problems.extra) > 0 { 67 | return problems 68 | } 69 | return nil 70 | } 71 | -------------------------------------------------------------------------------- /fs/fstestutil/debug.go: -------------------------------------------------------------------------------- 1 | package fstestutil 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | "strconv" 7 | 8 | "github.com/seaweedfs/fuse" 9 | ) 10 | 11 | type flagDebug bool 12 | 13 | var debug flagDebug 14 | 15 | var _ = flag.Value(&debug) 16 | 17 | func (f *flagDebug) IsBoolFlag() bool { 18 | return true 19 | } 20 | 21 | func nop(msg interface{}) {} 22 | 23 | func (f *flagDebug) Set(s string) error { 24 | v, err := strconv.ParseBool(s) 25 | if err != nil { 26 | return err 27 | } 28 | *f = flagDebug(v) 29 | if v { 30 | fuse.Debug = logMsg 31 | } else { 32 | fuse.Debug = nop 33 | } 34 | return nil 35 | } 36 | 37 | func (f *flagDebug) String() string { 38 | return strconv.FormatBool(bool(*f)) 39 | } 40 | 41 | func logMsg(msg interface{}) { 42 | log.Printf("FUSE: %s\n", msg) 43 | } 44 | 45 | func init() { 46 | flag.Var(&debug, "fuse.debug", "log FUSE processing details") 47 | } 48 | 49 | // DebugByDefault changes the default of the `-fuse.debug` flag to 50 | // true. 51 | // 52 | // This package registers a command line flag `-fuse.debug` and when 53 | // run with that flag (and activated inside the tests), logs FUSE 54 | // debug messages. 55 | // 56 | // This is disabled by default, as most callers probably won't care 57 | // about FUSE details. Use DebugByDefault for tests where you'd 58 | // normally be passing `-fuse.debug` all the time anyway. 59 | // 60 | // Call from an init function. 61 | func DebugByDefault() { 62 | f := flag.Lookup("fuse.debug") 63 | f.DefValue = "true" 64 | f.Value.Set(f.DefValue) 65 | } 66 | -------------------------------------------------------------------------------- /fs/fstestutil/doc.go: -------------------------------------------------------------------------------- 1 | package fstestutil // import "github.com/seaweedfs/fuse/fs/fstestutil" 2 | -------------------------------------------------------------------------------- /fs/fstestutil/mounted.go: -------------------------------------------------------------------------------- 1 | package fstestutil 2 | 3 | import ( 4 | "errors" 5 | "io/ioutil" 6 | "log" 7 | "os" 8 | "testing" 9 | "time" 10 | 11 | "github.com/seaweedfs/fuse" 12 | "github.com/seaweedfs/fuse/fs" 13 | ) 14 | 15 | // Mount contains information about the mount for the test to use. 16 | type Mount struct { 17 | // Dir is the temporary directory where the filesystem is mounted. 18 | Dir string 19 | 20 | Conn *fuse.Conn 21 | Server *fs.Server 22 | 23 | // Error will receive the return value of Serve. 24 | Error <-chan error 25 | 26 | done <-chan struct{} 27 | closed bool 28 | } 29 | 30 | // Close unmounts the filesystem and waits for fs.Serve to return. Any 31 | // returned error will be stored in Err. It is safe to call Close 32 | // multiple times. 33 | func (mnt *Mount) Close() { 34 | if mnt.closed { 35 | return 36 | } 37 | mnt.closed = true 38 | for tries := 0; tries < 1000; tries++ { 39 | err := fuse.Unmount(mnt.Dir) 40 | if err != nil { 41 | // TODO do more than log? 42 | log.Printf("unmount error: %v", err) 43 | time.Sleep(10 * time.Millisecond) 44 | continue 45 | } 46 | break 47 | } 48 | <-mnt.done 49 | mnt.Conn.Close() 50 | os.Remove(mnt.Dir) 51 | } 52 | 53 | // MountedFunc mounts a filesystem at a temporary directory. The 54 | // filesystem used is constructed by calling a function, to allow 55 | // storing fuse.Conn and fs.Server in the FS. 56 | // 57 | // It also waits until the filesystem is known to be visible (OS X 58 | // workaround). 59 | // 60 | // After successful return, caller must clean up by calling Close. 61 | func MountedFunc(fn func(*Mount) fs.FS, conf *fs.Config, options ...fuse.MountOption) (*Mount, error) { 62 | dir, err := ioutil.TempDir("", "fusetest") 63 | if err != nil { 64 | return nil, err 65 | } 66 | c, err := fuse.Mount(dir, options...) 67 | if err != nil { 68 | return nil, err 69 | } 70 | server := fs.New(c, conf) 71 | done := make(chan struct{}) 72 | serveErr := make(chan error, 1) 73 | mnt := &Mount{ 74 | Dir: dir, 75 | Conn: c, 76 | Server: server, 77 | Error: serveErr, 78 | done: done, 79 | } 80 | filesys := fn(mnt) 81 | go func() { 82 | defer close(done) 83 | serveErr <- server.Serve(filesys) 84 | }() 85 | 86 | select { 87 | case <-mnt.Conn.Ready: 88 | if err := mnt.Conn.MountError; err != nil { 89 | return nil, err 90 | } 91 | return mnt, nil 92 | case err = <-mnt.Error: 93 | // Serve quit early 94 | if err != nil { 95 | return nil, err 96 | } 97 | return nil, errors.New("Serve exited early") 98 | } 99 | } 100 | 101 | // Mounted mounts the fuse.Server at a temporary directory. 102 | // 103 | // It also waits until the filesystem is known to be visible (OS X 104 | // workaround). 105 | // 106 | // After successful return, caller must clean up by calling Close. 107 | func Mounted(filesys fs.FS, conf *fs.Config, options ...fuse.MountOption) (*Mount, error) { 108 | fn := func(*Mount) fs.FS { return filesys } 109 | return MountedFunc(fn, conf, options...) 110 | } 111 | 112 | // MountedFuncT mounts a filesystem at a temporary directory, 113 | // directing it's debug log to the testing logger. 114 | // 115 | // See MountedFunc for usage. 116 | // 117 | // The debug log is not enabled by default. Use `-fuse.debug` or call 118 | // DebugByDefault to enable. 119 | func MountedFuncT(t testing.TB, fn func(*Mount) fs.FS, conf *fs.Config, options ...fuse.MountOption) (*Mount, error) { 120 | if conf == nil { 121 | conf = &fs.Config{} 122 | } 123 | if debug && conf.Debug == nil { 124 | conf.Debug = func(msg interface{}) { 125 | t.Logf("FUSE: %s", msg) 126 | } 127 | } 128 | return MountedFunc(fn, conf, options...) 129 | } 130 | 131 | // MountedT mounts the filesystem at a temporary directory, 132 | // directing it's debug log to the testing logger. 133 | // 134 | // See Mounted for usage. 135 | // 136 | // The debug log is not enabled by default. Use `-fuse.debug` or call 137 | // DebugByDefault to enable. 138 | func MountedT(t testing.TB, filesys fs.FS, conf *fs.Config, options ...fuse.MountOption) (*Mount, error) { 139 | fn := func(*Mount) fs.FS { return filesys } 140 | return MountedFuncT(t, fn, conf, options...) 141 | } 142 | -------------------------------------------------------------------------------- /fs/fstestutil/mountinfo.go: -------------------------------------------------------------------------------- 1 | package fstestutil 2 | 3 | // MountInfo describes a mounted file system. 4 | type MountInfo struct { 5 | FSName string 6 | Type string 7 | } 8 | 9 | // GetMountInfo finds information about the mount at mnt. It is 10 | // intended for use by tests only, and only fetches information 11 | // relevant to the current tests. 12 | func GetMountInfo(mnt string) (*MountInfo, error) { 13 | return getMountInfo(mnt) 14 | } 15 | 16 | // cstr converts a nil-terminated C string into a Go string 17 | func cstr(ca []int8) string { 18 | s := make([]byte, 0, len(ca)) 19 | for _, c := range ca { 20 | if c == 0x00 { 21 | break 22 | } 23 | s = append(s, byte(c)) 24 | } 25 | return string(s) 26 | } 27 | -------------------------------------------------------------------------------- /fs/fstestutil/mountinfo_darwin.go: -------------------------------------------------------------------------------- 1 | package fstestutil 2 | 3 | import ( 4 | "regexp" 5 | "syscall" 6 | ) 7 | 8 | var re = regexp.MustCompile(`\\(.)`) 9 | 10 | // unescape removes backslash-escaping. The escaped characters are not 11 | // mapped in any way; that is, unescape(`\n` ) == `n`. 12 | func unescape(s string) string { 13 | return re.ReplaceAllString(s, `$1`) 14 | } 15 | 16 | func getMountInfo(mnt string) (*MountInfo, error) { 17 | var st syscall.Statfs_t 18 | err := syscall.Statfs(mnt, &st) 19 | if err != nil { 20 | return nil, err 21 | } 22 | i := &MountInfo{ 23 | // osx getmntent(3) fails to un-escape the data, so we do it.. 24 | // this might lead to double-unescaping in the future. fun. 25 | // TestMountOptionFSNameEvilBackslashDouble checks for that. 26 | FSName: unescape(cstr(st.Mntfromname[:])), 27 | } 28 | return i, nil 29 | } 30 | -------------------------------------------------------------------------------- /fs/fstestutil/mountinfo_freebsd.go: -------------------------------------------------------------------------------- 1 | package fstestutil 2 | 3 | import "errors" 4 | 5 | func getMountInfo(mnt string) (*MountInfo, error) { 6 | return nil, errors.New("FreeBSD has no useful mount information") 7 | } 8 | -------------------------------------------------------------------------------- /fs/fstestutil/mountinfo_linux.go: -------------------------------------------------------------------------------- 1 | package fstestutil 2 | 3 | import ( 4 | "errors" 5 | "io/ioutil" 6 | "strings" 7 | ) 8 | 9 | // Linux /proc/mounts shows current mounts. 10 | // Same format as /etc/fstab. Quoting getmntent(3): 11 | // 12 | // Since fields in the mtab and fstab files are separated by whitespace, 13 | // octal escapes are used to represent the four characters space (\040), 14 | // tab (\011), newline (\012) and backslash (\134) in those files when 15 | // they occur in one of the four strings in a mntent structure. 16 | // 17 | // http://linux.die.net/man/3/getmntent 18 | 19 | var fstabUnescape = strings.NewReplacer( 20 | `\040`, "\040", 21 | `\011`, "\011", 22 | `\012`, "\012", 23 | `\134`, "\134", 24 | ) 25 | 26 | var errNotFound = errors.New("mount not found") 27 | 28 | func getMountInfo(mnt string) (*MountInfo, error) { 29 | data, err := ioutil.ReadFile("/proc/mounts") 30 | if err != nil { 31 | return nil, err 32 | } 33 | for _, line := range strings.Split(string(data), "\n") { 34 | fields := strings.Fields(line) 35 | if len(fields) < 3 { 36 | continue 37 | } 38 | // Fields are: fsname dir type opts freq passno 39 | fsname := fstabUnescape.Replace(fields[0]) 40 | dir := fstabUnescape.Replace(fields[1]) 41 | fstype := fstabUnescape.Replace(fields[2]) 42 | if mnt == dir { 43 | info := &MountInfo{ 44 | FSName: fsname, 45 | Type: fstype, 46 | } 47 | return info, nil 48 | } 49 | } 50 | return nil, errNotFound 51 | } 52 | -------------------------------------------------------------------------------- /fs/fstestutil/record/buffer.go: -------------------------------------------------------------------------------- 1 | package record 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "sync" 7 | ) 8 | 9 | // Buffer is like bytes.Buffer but safe to access from multiple 10 | // goroutines. 11 | type Buffer struct { 12 | mu sync.Mutex 13 | buf bytes.Buffer 14 | } 15 | 16 | var _ = io.Writer(&Buffer{}) 17 | 18 | func (b *Buffer) Write(p []byte) (n int, err error) { 19 | b.mu.Lock() 20 | defer b.mu.Unlock() 21 | return b.buf.Write(p) 22 | } 23 | 24 | func (b *Buffer) Bytes() []byte { 25 | b.mu.Lock() 26 | defer b.mu.Unlock() 27 | return b.buf.Bytes() 28 | } 29 | -------------------------------------------------------------------------------- /fs/fstestutil/record/record.go: -------------------------------------------------------------------------------- 1 | package record // import "github.com/seaweedfs/fuse/fs/fstestutil/record" 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | "sync/atomic" 7 | 8 | "github.com/seaweedfs/fuse" 9 | "github.com/seaweedfs/fuse/fs" 10 | ) 11 | 12 | // Writes gathers data from FUSE Write calls. 13 | type Writes struct { 14 | buf Buffer 15 | } 16 | 17 | var _ = fs.HandleWriter(&Writes{}) 18 | 19 | func (w *Writes) Write(ctx context.Context, req *fuse.WriteRequest, resp *fuse.WriteResponse) error { 20 | n, err := w.buf.Write(req.Data) 21 | resp.Size = n 22 | if err != nil { 23 | return err 24 | } 25 | return nil 26 | } 27 | 28 | func (w *Writes) RecordedWriteData() []byte { 29 | return w.buf.Bytes() 30 | } 31 | 32 | // Counter records number of times a thing has occurred. 33 | type Counter struct { 34 | count uint32 35 | } 36 | 37 | func (r *Counter) Inc() { 38 | atomic.AddUint32(&r.count, 1) 39 | } 40 | 41 | func (r *Counter) Count() uint32 { 42 | return atomic.LoadUint32(&r.count) 43 | } 44 | 45 | // MarkRecorder records whether a thing has occurred. 46 | type MarkRecorder struct { 47 | count Counter 48 | } 49 | 50 | func (r *MarkRecorder) Mark() { 51 | r.count.Inc() 52 | } 53 | 54 | func (r *MarkRecorder) Recorded() bool { 55 | return r.count.Count() > 0 56 | } 57 | 58 | // Flushes notes whether a FUSE Flush call has been seen. 59 | type Flushes struct { 60 | rec MarkRecorder 61 | } 62 | 63 | var _ = fs.HandleFlusher(&Flushes{}) 64 | 65 | func (r *Flushes) Flush(ctx context.Context, req *fuse.FlushRequest) error { 66 | r.rec.Mark() 67 | return nil 68 | } 69 | 70 | func (r *Flushes) RecordedFlush() bool { 71 | return r.rec.Recorded() 72 | } 73 | 74 | type Recorder struct { 75 | mu sync.Mutex 76 | val interface{} 77 | } 78 | 79 | // Record that we've seen value. A nil value is indistinguishable from 80 | // no value recorded. 81 | func (r *Recorder) Record(value interface{}) { 82 | r.mu.Lock() 83 | r.val = value 84 | r.mu.Unlock() 85 | } 86 | 87 | func (r *Recorder) Recorded() interface{} { 88 | r.mu.Lock() 89 | val := r.val 90 | r.mu.Unlock() 91 | return val 92 | } 93 | 94 | type RequestRecorder struct { 95 | rec Recorder 96 | } 97 | 98 | // Record a fuse.Request, after zeroing header fields that are hard to 99 | // reproduce. 100 | // 101 | // Make sure to record a copy, not the original request. 102 | func (r *RequestRecorder) RecordRequest(req fuse.Request) { 103 | hdr := req.Hdr() 104 | *hdr = fuse.Header{} 105 | r.rec.Record(req) 106 | } 107 | 108 | func (r *RequestRecorder) Recorded() fuse.Request { 109 | val := r.rec.Recorded() 110 | if val == nil { 111 | return nil 112 | } 113 | return val.(fuse.Request) 114 | } 115 | 116 | // Setattrs records a Setattr request and its fields. 117 | type Setattrs struct { 118 | rec RequestRecorder 119 | } 120 | 121 | var _ = fs.NodeSetattrer(&Setattrs{}) 122 | 123 | func (r *Setattrs) Setattr(ctx context.Context, req *fuse.SetattrRequest, resp *fuse.SetattrResponse) error { 124 | tmp := *req 125 | r.rec.RecordRequest(&tmp) 126 | return nil 127 | } 128 | 129 | func (r *Setattrs) RecordedSetattr() fuse.SetattrRequest { 130 | val := r.rec.Recorded() 131 | if val == nil { 132 | return fuse.SetattrRequest{} 133 | } 134 | return *(val.(*fuse.SetattrRequest)) 135 | } 136 | 137 | // Fsyncs records an Fsync request and its fields. 138 | type Fsyncs struct { 139 | rec RequestRecorder 140 | } 141 | 142 | var _ = fs.NodeFsyncer(&Fsyncs{}) 143 | 144 | func (r *Fsyncs) Fsync(ctx context.Context, req *fuse.FsyncRequest) error { 145 | tmp := *req 146 | r.rec.RecordRequest(&tmp) 147 | return nil 148 | } 149 | 150 | func (r *Fsyncs) RecordedFsync() fuse.FsyncRequest { 151 | val := r.rec.Recorded() 152 | if val == nil { 153 | return fuse.FsyncRequest{} 154 | } 155 | return *(val.(*fuse.FsyncRequest)) 156 | } 157 | 158 | // Mkdirs records a Mkdir request and its fields. 159 | type Mkdirs struct { 160 | rec RequestRecorder 161 | } 162 | 163 | var _ = fs.NodeMkdirer(&Mkdirs{}) 164 | 165 | // Mkdir records the request and returns an error. Most callers should 166 | // wrap this call in a function that returns a more useful result. 167 | func (r *Mkdirs) Mkdir(ctx context.Context, req *fuse.MkdirRequest) (fs.Node, error) { 168 | tmp := *req 169 | r.rec.RecordRequest(&tmp) 170 | return nil, fuse.EIO 171 | } 172 | 173 | // RecordedMkdir returns information about the Mkdir request. 174 | // If no request was seen, returns a zero value. 175 | func (r *Mkdirs) RecordedMkdir() fuse.MkdirRequest { 176 | val := r.rec.Recorded() 177 | if val == nil { 178 | return fuse.MkdirRequest{} 179 | } 180 | return *(val.(*fuse.MkdirRequest)) 181 | } 182 | 183 | // Symlinks records a Symlink request and its fields. 184 | type Symlinks struct { 185 | rec RequestRecorder 186 | } 187 | 188 | var _ = fs.NodeSymlinker(&Symlinks{}) 189 | 190 | // Symlink records the request and returns an error. Most callers should 191 | // wrap this call in a function that returns a more useful result. 192 | func (r *Symlinks) Symlink(ctx context.Context, req *fuse.SymlinkRequest) (fs.Node, error) { 193 | tmp := *req 194 | r.rec.RecordRequest(&tmp) 195 | return nil, fuse.EIO 196 | } 197 | 198 | // RecordedSymlink returns information about the Symlink request. 199 | // If no request was seen, returns a zero value. 200 | func (r *Symlinks) RecordedSymlink() fuse.SymlinkRequest { 201 | val := r.rec.Recorded() 202 | if val == nil { 203 | return fuse.SymlinkRequest{} 204 | } 205 | return *(val.(*fuse.SymlinkRequest)) 206 | } 207 | 208 | // Links records a Link request and its fields. 209 | type Links struct { 210 | rec RequestRecorder 211 | } 212 | 213 | var _ = fs.NodeLinker(&Links{}) 214 | 215 | // Link records the request and returns an error. Most callers should 216 | // wrap this call in a function that returns a more useful result. 217 | func (r *Links) Link(ctx context.Context, req *fuse.LinkRequest, old fs.Node) (fs.Node, error) { 218 | tmp := *req 219 | r.rec.RecordRequest(&tmp) 220 | return nil, fuse.EIO 221 | } 222 | 223 | // RecordedLink returns information about the Link request. 224 | // If no request was seen, returns a zero value. 225 | func (r *Links) RecordedLink() fuse.LinkRequest { 226 | val := r.rec.Recorded() 227 | if val == nil { 228 | return fuse.LinkRequest{} 229 | } 230 | return *(val.(*fuse.LinkRequest)) 231 | } 232 | 233 | // Mknods records a Mknod request and its fields. 234 | type Mknods struct { 235 | rec RequestRecorder 236 | } 237 | 238 | var _ = fs.NodeMknoder(&Mknods{}) 239 | 240 | // Mknod records the request and returns an error. Most callers should 241 | // wrap this call in a function that returns a more useful result. 242 | func (r *Mknods) Mknod(ctx context.Context, req *fuse.MknodRequest) (fs.Node, error) { 243 | tmp := *req 244 | r.rec.RecordRequest(&tmp) 245 | return nil, fuse.EIO 246 | } 247 | 248 | // RecordedMknod returns information about the Mknod request. 249 | // If no request was seen, returns a zero value. 250 | func (r *Mknods) RecordedMknod() fuse.MknodRequest { 251 | val := r.rec.Recorded() 252 | if val == nil { 253 | return fuse.MknodRequest{} 254 | } 255 | return *(val.(*fuse.MknodRequest)) 256 | } 257 | 258 | // Opens records a Open request and its fields. 259 | type Opens struct { 260 | rec RequestRecorder 261 | } 262 | 263 | var _ = fs.NodeOpener(&Opens{}) 264 | 265 | // Open records the request and returns an error. Most callers should 266 | // wrap this call in a function that returns a more useful result. 267 | func (r *Opens) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) { 268 | tmp := *req 269 | r.rec.RecordRequest(&tmp) 270 | return nil, fuse.EIO 271 | } 272 | 273 | // RecordedOpen returns information about the Open request. 274 | // If no request was seen, returns a zero value. 275 | func (r *Opens) RecordedOpen() fuse.OpenRequest { 276 | val := r.rec.Recorded() 277 | if val == nil { 278 | return fuse.OpenRequest{} 279 | } 280 | return *(val.(*fuse.OpenRequest)) 281 | } 282 | 283 | // Getxattrs records a Getxattr request and its fields. 284 | type Getxattrs struct { 285 | rec RequestRecorder 286 | } 287 | 288 | var _ = fs.NodeGetxattrer(&Getxattrs{}) 289 | 290 | // Getxattr records the request and returns an error. Most callers should 291 | // wrap this call in a function that returns a more useful result. 292 | func (r *Getxattrs) Getxattr(ctx context.Context, req *fuse.GetxattrRequest, resp *fuse.GetxattrResponse) error { 293 | tmp := *req 294 | r.rec.RecordRequest(&tmp) 295 | return fuse.ErrNoXattr 296 | } 297 | 298 | // RecordedGetxattr returns information about the Getxattr request. 299 | // If no request was seen, returns a zero value. 300 | func (r *Getxattrs) RecordedGetxattr() fuse.GetxattrRequest { 301 | val := r.rec.Recorded() 302 | if val == nil { 303 | return fuse.GetxattrRequest{} 304 | } 305 | return *(val.(*fuse.GetxattrRequest)) 306 | } 307 | 308 | // Listxattrs records a Listxattr request and its fields. 309 | type Listxattrs struct { 310 | rec RequestRecorder 311 | } 312 | 313 | var _ = fs.NodeListxattrer(&Listxattrs{}) 314 | 315 | // Listxattr records the request and returns an error. Most callers should 316 | // wrap this call in a function that returns a more useful result. 317 | func (r *Listxattrs) Listxattr(ctx context.Context, req *fuse.ListxattrRequest, resp *fuse.ListxattrResponse) error { 318 | tmp := *req 319 | r.rec.RecordRequest(&tmp) 320 | return fuse.ErrNoXattr 321 | } 322 | 323 | // RecordedListxattr returns information about the Listxattr request. 324 | // If no request was seen, returns a zero value. 325 | func (r *Listxattrs) RecordedListxattr() fuse.ListxattrRequest { 326 | val := r.rec.Recorded() 327 | if val == nil { 328 | return fuse.ListxattrRequest{} 329 | } 330 | return *(val.(*fuse.ListxattrRequest)) 331 | } 332 | 333 | // Setxattrs records a Setxattr request and its fields. 334 | type Setxattrs struct { 335 | rec RequestRecorder 336 | } 337 | 338 | var _ = fs.NodeSetxattrer(&Setxattrs{}) 339 | 340 | // Setxattr records the request and returns an error. Most callers should 341 | // wrap this call in a function that returns a more useful result. 342 | func (r *Setxattrs) Setxattr(ctx context.Context, req *fuse.SetxattrRequest) error { 343 | tmp := *req 344 | // The byte slice points to memory that will be reused, so make a 345 | // deep copy. 346 | tmp.Xattr = append([]byte(nil), req.Xattr...) 347 | r.rec.RecordRequest(&tmp) 348 | return nil 349 | } 350 | 351 | // RecordedSetxattr returns information about the Setxattr request. 352 | // If no request was seen, returns a zero value. 353 | func (r *Setxattrs) RecordedSetxattr() fuse.SetxattrRequest { 354 | val := r.rec.Recorded() 355 | if val == nil { 356 | return fuse.SetxattrRequest{} 357 | } 358 | return *(val.(*fuse.SetxattrRequest)) 359 | } 360 | 361 | // Removexattrs records a Removexattr request and its fields. 362 | type Removexattrs struct { 363 | rec RequestRecorder 364 | } 365 | 366 | var _ = fs.NodeRemovexattrer(&Removexattrs{}) 367 | 368 | // Removexattr records the request and returns an error. Most callers should 369 | // wrap this call in a function that returns a more useful result. 370 | func (r *Removexattrs) Removexattr(ctx context.Context, req *fuse.RemovexattrRequest) error { 371 | tmp := *req 372 | r.rec.RecordRequest(&tmp) 373 | return nil 374 | } 375 | 376 | // RecordedRemovexattr returns information about the Removexattr request. 377 | // If no request was seen, returns a zero value. 378 | func (r *Removexattrs) RecordedRemovexattr() fuse.RemovexattrRequest { 379 | val := r.rec.Recorded() 380 | if val == nil { 381 | return fuse.RemovexattrRequest{} 382 | } 383 | return *(val.(*fuse.RemovexattrRequest)) 384 | } 385 | 386 | // Creates records a Create request and its fields. 387 | type Creates struct { 388 | rec RequestRecorder 389 | } 390 | 391 | var _ = fs.NodeCreater(&Creates{}) 392 | 393 | // Create records the request and returns an error. Most callers should 394 | // wrap this call in a function that returns a more useful result. 395 | func (r *Creates) Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (fs.Node, fs.Handle, error) { 396 | tmp := *req 397 | r.rec.RecordRequest(&tmp) 398 | return nil, nil, fuse.EIO 399 | } 400 | 401 | // RecordedCreate returns information about the Create request. 402 | // If no request was seen, returns a zero value. 403 | func (r *Creates) RecordedCreate() fuse.CreateRequest { 404 | val := r.rec.Recorded() 405 | if val == nil { 406 | return fuse.CreateRequest{} 407 | } 408 | return *(val.(*fuse.CreateRequest)) 409 | } 410 | -------------------------------------------------------------------------------- /fs/fstestutil/record/wait.go: -------------------------------------------------------------------------------- 1 | package record 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | "time" 7 | 8 | "github.com/seaweedfs/fuse" 9 | "github.com/seaweedfs/fuse/fs" 10 | ) 11 | 12 | type nothing struct{} 13 | 14 | // ReleaseWaiter notes whether a FUSE Release call has been seen. 15 | // 16 | // Releases are not guaranteed to happen synchronously with any client 17 | // call, so they must be waited for. 18 | type ReleaseWaiter struct { 19 | once sync.Once 20 | seen chan nothing 21 | } 22 | 23 | var _ = fs.HandleReleaser(&ReleaseWaiter{}) 24 | 25 | func (r *ReleaseWaiter) init() { 26 | r.once.Do(func() { 27 | r.seen = make(chan nothing, 1) 28 | }) 29 | } 30 | 31 | func (r *ReleaseWaiter) Release(ctx context.Context, req *fuse.ReleaseRequest) error { 32 | r.init() 33 | close(r.seen) 34 | return nil 35 | } 36 | 37 | // WaitForRelease waits for Release to be called. 38 | // 39 | // With zero duration, wait forever. Otherwise, timeout early 40 | // in a more controller way than `-test.timeout`. 41 | // 42 | // Returns whether a Release was seen. Always true if dur==0. 43 | func (r *ReleaseWaiter) WaitForRelease(dur time.Duration) bool { 44 | r.init() 45 | var timeout <-chan time.Time 46 | if dur > 0 { 47 | timeout = time.After(dur) 48 | } 49 | select { 50 | case <-r.seen: 51 | return true 52 | case <-timeout: 53 | return false 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /fs/fstestutil/testfs.go: -------------------------------------------------------------------------------- 1 | package fstestutil 2 | 3 | import ( 4 | "context" 5 | "os" 6 | 7 | "github.com/seaweedfs/fuse" 8 | "github.com/seaweedfs/fuse/fs" 9 | ) 10 | 11 | // SimpleFS is a trivial FS that just implements the Root method. 12 | type SimpleFS struct { 13 | Node fs.Node 14 | } 15 | 16 | var _ = fs.FS(SimpleFS{}) 17 | 18 | func (f SimpleFS) Root() (fs.Node, error) { 19 | return f.Node, nil 20 | } 21 | 22 | // File can be embedded in a struct to make it look like a file. 23 | type File struct{} 24 | 25 | func (f File) Attr(ctx context.Context, a *fuse.Attr) error { 26 | a.Mode = 0666 27 | return nil 28 | } 29 | 30 | // Dir can be embedded in a struct to make it look like a directory. 31 | type Dir struct{} 32 | 33 | func (f Dir) Attr(ctx context.Context, a *fuse.Attr) error { 34 | a.Mode = os.ModeDir | 0777 35 | return nil 36 | } 37 | 38 | // ChildMap is a directory with child nodes looked up from a map. 39 | type ChildMap map[string]fs.Node 40 | 41 | var _ = fs.Node(&ChildMap{}) 42 | var _ = fs.NodeStringLookuper(&ChildMap{}) 43 | 44 | func (f *ChildMap) Attr(ctx context.Context, a *fuse.Attr) error { 45 | a.Mode = os.ModeDir | 0777 46 | return nil 47 | } 48 | 49 | func (f *ChildMap) Lookup(ctx context.Context, name string) (fs.Node, error) { 50 | child, ok := (*f)[name] 51 | if !ok { 52 | return nil, fuse.ENOENT 53 | } 54 | return child, nil 55 | } 56 | -------------------------------------------------------------------------------- /fs/helpers_test.go: -------------------------------------------------------------------------------- 1 | package fs_test 2 | 3 | import ( 4 | "errors" 5 | "flag" 6 | "os" 7 | "os/exec" 8 | "path/filepath" 9 | "testing" 10 | ) 11 | 12 | var childHelpers = map[string]func(){} 13 | 14 | type childProcess struct { 15 | name string 16 | fn func() 17 | } 18 | 19 | var _ flag.Value = (*childProcess)(nil) 20 | 21 | func (c *childProcess) String() string { 22 | return c.name 23 | } 24 | 25 | func (c *childProcess) Set(s string) error { 26 | fn, ok := childHelpers[s] 27 | if !ok { 28 | return errors.New("helper not found") 29 | } 30 | c.name = s 31 | c.fn = fn 32 | return nil 33 | } 34 | 35 | var childMode childProcess 36 | 37 | func init() { 38 | flag.Var(&childMode, "fuse.internal.child", "internal use only") 39 | } 40 | 41 | // childCmd prepares a test function to be run in a subprocess, with 42 | // childMode set to true. Caller must still call Run or Start. 43 | // 44 | // Re-using the test executable as the subprocess is useful because 45 | // now test executables can e.g. be cross-compiled, transferred 46 | // between hosts, and run in settings where the whole Go development 47 | // environment is not installed. 48 | func childCmd(childName string) (*exec.Cmd, error) { 49 | // caller may set cwd, so we can't rely on relative paths 50 | executable, err := filepath.Abs(os.Args[0]) 51 | if err != nil { 52 | return nil, err 53 | } 54 | cmd := exec.Command(executable, "-fuse.internal.child="+childName) 55 | cmd.Stdout = os.Stdout 56 | cmd.Stderr = os.Stderr 57 | return cmd, nil 58 | } 59 | 60 | func TestMain(m *testing.M) { 61 | flag.Parse() 62 | if childMode.fn != nil { 63 | childMode.fn() 64 | os.Exit(0) 65 | } 66 | os.Exit(m.Run()) 67 | } 68 | -------------------------------------------------------------------------------- /fs/serve.go: -------------------------------------------------------------------------------- 1 | // FUSE service loop, for servers that wish to use it. 2 | 3 | package fs // import "github.com/seaweedfs/fuse/fs" 4 | 5 | import ( 6 | "bytes" 7 | "context" 8 | "encoding/binary" 9 | "errors" 10 | "fmt" 11 | "hash/fnv" 12 | "io" 13 | "log" 14 | "reflect" 15 | "runtime" 16 | "strings" 17 | "sync" 18 | "syscall" 19 | "time" 20 | 21 | "github.com/seaweedfs/fuse" 22 | "github.com/seaweedfs/fuse/fuseutil" 23 | "golang.org/x/sys/unix" 24 | ) 25 | 26 | const ( 27 | attrValidTime = 1 * time.Minute 28 | entryValidTime = 1 * time.Minute 29 | ) 30 | 31 | // TODO: FINISH DOCS 32 | 33 | // An FS is the interface required of a file system. 34 | // 35 | // Other FUSE requests can be handled by implementing methods from the 36 | // FS* interfaces, for example FSStatfser. 37 | type FS interface { 38 | // Root is called to obtain the Node for the file system root. 39 | Root() (Node, error) 40 | } 41 | 42 | type FSStatfser interface { 43 | // Statfs is called to obtain file system metadata. 44 | // It should write that data to resp. 45 | Statfs(ctx context.Context, req *fuse.StatfsRequest, resp *fuse.StatfsResponse) error 46 | } 47 | 48 | type FSDestroyer interface { 49 | // Destroy is called when the file system is shutting down. 50 | // 51 | // Linux only sends this request for block device backed (fuseblk) 52 | // filesystems, to allow them to flush writes to disk before the 53 | // unmount completes. 54 | Destroy() 55 | } 56 | 57 | type FSInodeGenerator interface { 58 | // GenerateInode is called to pick a dynamic inode number when it 59 | // would otherwise be 0. 60 | // 61 | // Not all filesystems bother tracking inodes, but FUSE requires 62 | // the inode to be set, and fewer duplicates in general makes UNIX 63 | // tools work better. 64 | // 65 | // Operations where the nodes may return 0 inodes include Getattr, 66 | // Setattr and ReadDir. 67 | // 68 | // If FS does not implement FSInodeGenerator, GenerateDynamicInode 69 | // is used. 70 | // 71 | // Implementing this is useful to e.g. constrain the range of 72 | // inode values used for dynamic inodes. 73 | GenerateInode(parentInode uint64, name string) uint64 74 | } 75 | 76 | // A Node is the interface required of a file or directory. 77 | // See the documentation for type FS for general information 78 | // pertaining to all methods. 79 | // 80 | // A Node must be usable as a map key, that is, it cannot be a 81 | // function, map or slice. 82 | // 83 | // Other FUSE requests can be handled by implementing methods from the 84 | // Node* interfaces, for example NodeOpener. 85 | // 86 | // Methods returning Node should take care to return the same Node 87 | // when the result is logically the same instance. Without this, each 88 | // Node will get a new NodeID, causing spurious cache invalidations, 89 | // extra lookups and aliasing anomalies. This may not matter for a 90 | // simple, read-only filesystem. 91 | type Node interface { 92 | // Attr fills attr with the standard metadata for the node. 93 | // 94 | // Fields with reasonable defaults are prepopulated. For example, 95 | // all times are set to a fixed moment when the program started. 96 | // 97 | // If Inode is left as 0, a dynamic inode number is chosen. 98 | // 99 | // The result may be cached for the duration set in Valid. 100 | Attr(ctx context.Context, attr *fuse.Attr) error 101 | } 102 | 103 | type NodeIdentifier interface { 104 | // Id returns the node inode value 105 | Id() uint64 106 | } 107 | 108 | type NodeGetattrer interface { 109 | // Getattr obtains the standard metadata for the receiver. 110 | // It should store that metadata in resp. 111 | // 112 | // If this method is not implemented, the attributes will be 113 | // generated based on Attr(), with zero values filled in. 114 | Getattr(ctx context.Context, req *fuse.GetattrRequest, resp *fuse.GetattrResponse) error 115 | } 116 | 117 | type NodeSetattrer interface { 118 | // Setattr sets the standard metadata for the receiver. 119 | // 120 | // Note, this is also used to communicate changes in the size of 121 | // the file, outside of Writes. 122 | // 123 | // req.Valid is a bitmask of what fields are actually being set. 124 | // For example, the method should not change the mode of the file 125 | // unless req.Valid.Mode() is true. 126 | Setattr(ctx context.Context, req *fuse.SetattrRequest, resp *fuse.SetattrResponse) error 127 | } 128 | 129 | type NodeSymlinker interface { 130 | // Symlink creates a new symbolic link in the receiver, which must be a directory. 131 | // 132 | // TODO is the above true about directories? 133 | Symlink(ctx context.Context, req *fuse.SymlinkRequest) (Node, error) 134 | } 135 | 136 | // This optional request will be called only for symbolic link nodes. 137 | type NodeReadlinker interface { 138 | // Readlink reads a symbolic link. 139 | Readlink(ctx context.Context, req *fuse.ReadlinkRequest) (string, error) 140 | } 141 | 142 | type NodeLinker interface { 143 | // Link creates a new directory entry in the receiver based on an 144 | // existing Node. Receiver must be a directory. 145 | Link(ctx context.Context, req *fuse.LinkRequest, old Node) (Node, error) 146 | } 147 | 148 | type NodeRemover interface { 149 | // Remove removes the entry with the given name from 150 | // the receiver, which must be a directory. The entry to be removed 151 | // may correspond to a file (unlink) or to a directory (rmdir). 152 | Remove(ctx context.Context, req *fuse.RemoveRequest) error 153 | } 154 | 155 | type NodeAccesser interface { 156 | // Access checks whether the calling context has permission for 157 | // the given operations on the receiver. If so, Access should 158 | // return nil. If not, Access should return EPERM. 159 | // 160 | // Note that this call affects the result of the access(2) system 161 | // call but not the open(2) system call. If Access is not 162 | // implemented, the Node behaves as if it always returns nil 163 | // (permission granted), relying on checks in Open instead. 164 | Access(ctx context.Context, req *fuse.AccessRequest) error 165 | } 166 | 167 | type NodeStringLookuper interface { 168 | // Lookup looks up a specific entry in the receiver, 169 | // which must be a directory. Lookup should return a Node 170 | // corresponding to the entry. If the name does not exist in 171 | // the directory, Lookup should return ENOENT. 172 | // 173 | // Lookup need not to handle the names "." and "..". 174 | Lookup(ctx context.Context, name string) (Node, error) 175 | } 176 | 177 | type NodeRequestLookuper interface { 178 | // Lookup looks up a specific entry in the receiver. 179 | // See NodeStringLookuper for more. 180 | Lookup(ctx context.Context, req *fuse.LookupRequest, resp *fuse.LookupResponse) (Node, error) 181 | } 182 | 183 | type NodeMkdirer interface { 184 | Mkdir(ctx context.Context, req *fuse.MkdirRequest) (Node, error) 185 | } 186 | 187 | type NodeOpener interface { 188 | // Open opens the receiver. After a successful open, a client 189 | // process has a file descriptor referring to this Handle. 190 | // 191 | // Open can also be also called on non-files. For example, 192 | // directories are Opened for ReadDir or fchdir(2). 193 | // 194 | // If this method is not implemented, the open will always 195 | // succeed, and the Node itself will be used as the Handle. 196 | // 197 | // XXX note about access. XXX OpenFlags. 198 | Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (Handle, error) 199 | } 200 | 201 | type NodeCreater interface { 202 | // Create creates a new directory entry in the receiver, which 203 | // must be a directory. 204 | Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (Node, Handle, error) 205 | } 206 | 207 | type NodeForgetter interface { 208 | // Forget about this node. This node will not receive further 209 | // method calls. 210 | // 211 | // Forget is not necessarily seen on unmount, as all nodes are 212 | // implicitly forgotten as part part of the unmount. 213 | Forget() 214 | } 215 | 216 | type NodeRenamer interface { 217 | Rename(ctx context.Context, req *fuse.RenameRequest, newDir Node) error 218 | } 219 | 220 | type NodeMknoder interface { 221 | Mknod(ctx context.Context, req *fuse.MknodRequest) (Node, error) 222 | } 223 | 224 | // TODO this should be on Handle not Node 225 | type NodeFsyncer interface { 226 | Fsync(ctx context.Context, req *fuse.FsyncRequest) error 227 | } 228 | 229 | type NodeGetxattrer interface { 230 | // Getxattr gets an extended attribute by the given name from the 231 | // node. 232 | // 233 | // If there is no xattr by that name, returns fuse.ErrNoXattr. 234 | Getxattr(ctx context.Context, req *fuse.GetxattrRequest, resp *fuse.GetxattrResponse) error 235 | } 236 | 237 | type NodeListxattrer interface { 238 | // Listxattr lists the extended attributes recorded for the node. 239 | Listxattr(ctx context.Context, req *fuse.ListxattrRequest, resp *fuse.ListxattrResponse) error 240 | } 241 | 242 | type NodeSetxattrer interface { 243 | // Setxattr sets an extended attribute with the given name and 244 | // value for the node. 245 | Setxattr(ctx context.Context, req *fuse.SetxattrRequest) error 246 | } 247 | 248 | type NodeRemovexattrer interface { 249 | // Removexattr removes an extended attribute for the name. 250 | // 251 | // If there is no xattr by that name, returns fuse.ErrNoXattr. 252 | Removexattr(ctx context.Context, req *fuse.RemovexattrRequest) error 253 | } 254 | 255 | var startTime = time.Now() 256 | 257 | func nodeAttr(ctx context.Context, n Node, attr *fuse.Attr) error { 258 | attr.Valid = attrValidTime 259 | attr.Nlink = 1 260 | attr.Atime = startTime 261 | attr.Mtime = startTime 262 | attr.Ctime = startTime 263 | attr.Crtime = startTime 264 | if err := n.Attr(ctx, attr); err != nil { 265 | return err 266 | } 267 | return nil 268 | } 269 | 270 | // A Handle is the interface required of an opened file or directory. 271 | // See the documentation for type FS for general information 272 | // pertaining to all methods. 273 | // 274 | // Other FUSE requests can be handled by implementing methods from the 275 | // Handle* interfaces. The most common to implement are HandleReader, 276 | // HandleReadDirer, and HandleWriter. 277 | // 278 | // TODO implement methods: Getlk, Setlk, Setlkw 279 | type Handle interface { 280 | } 281 | 282 | type HandleFlusher interface { 283 | // Flush is called each time the file or directory is closed. 284 | // Because there can be multiple file descriptors referring to a 285 | // single opened file, Flush can be called multiple times. 286 | Flush(ctx context.Context, req *fuse.FlushRequest) error 287 | } 288 | 289 | type HandleReadAller interface { 290 | ReadAll(ctx context.Context) ([]byte, error) 291 | } 292 | 293 | type HandleReadDirAller interface { 294 | ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) 295 | } 296 | 297 | type HandleReader interface { 298 | // Read requests to read data from the handle. 299 | // 300 | // There is a page cache in the kernel that normally submits only 301 | // page-aligned reads spanning one or more pages. However, you 302 | // should not rely on this. To see individual requests as 303 | // submitted by the file system clients, set OpenDirectIO. 304 | // 305 | // Note that reads beyond the size of the file as reported by Attr 306 | // are not even attempted (except in OpenDirectIO mode). 307 | Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error 308 | } 309 | 310 | type HandleWriter interface { 311 | // Write requests to write data into the handle at the given offset. 312 | // Store the amount of data written in resp.Size. 313 | // 314 | // There is a writeback page cache in the kernel that normally submits 315 | // only page-aligned writes spanning one or more pages. However, 316 | // you should not rely on this. To see individual requests as 317 | // submitted by the file system clients, set OpenDirectIO. 318 | // 319 | // Writes that grow the file are expected to update the file size 320 | // (as seen through Attr). Note that file size changes are 321 | // communicated also through Setattr. 322 | Write(ctx context.Context, req *fuse.WriteRequest, resp *fuse.WriteResponse) error 323 | } 324 | 325 | type HandleReleaser interface { 326 | Release(ctx context.Context, req *fuse.ReleaseRequest) error 327 | } 328 | 329 | type HandlePoller interface { 330 | // Poll checks whether the handle is currently ready for I/O, and 331 | // may request a wakeup when it is. 332 | // 333 | // Poll should always return quickly. Clients waiting for 334 | // readiness can be woken up by passing the return value of 335 | // PollRequest.Wakeup to fs.Server.NotifyPollWakeup or 336 | // fuse.Conn.NotifyPollWakeup. 337 | // 338 | // To allow supporting poll for only some of your Nodes/Handles, 339 | // the default behavior is to report immediate readiness. If your 340 | // FS does not support polling and you want to minimize needless 341 | // requests and log noise, implement NodePoller and return 342 | // syscall.ENOSYS. 343 | // 344 | // The Go runtime uses epoll-based I/O whenever possible, even for 345 | // regular files. 346 | Poll(ctx context.Context, req *fuse.PollRequest, resp *fuse.PollResponse) error 347 | } 348 | 349 | type NodePoller interface { 350 | // Poll checks whether the node is currently ready for I/O, and 351 | // may request a wakeup when it is. See HandlePoller. 352 | Poll(ctx context.Context, req *fuse.PollRequest, resp *fuse.PollResponse) error 353 | } 354 | 355 | // HandleLocker contains the common operations for all kinds of file 356 | // locks. See also lock family specific interfaces: HandleFlockLocker, 357 | // HandlePOSIXLocker. 358 | type HandleLocker interface { 359 | // Lock tries to acquire a lock on a byte range of the node. If a 360 | // conflicting lock is already held, returns syscall.EAGAIN. 361 | // 362 | // LockRequest.LockOwner is a file-unique identifier for this 363 | // lock, and will be seen in calls releasing this lock 364 | // (UnlockRequest, ReleaseRequest, FlushRequest) and also 365 | // in e.g. ReadRequest, WriteRequest. 366 | Lock(ctx context.Context, req *fuse.LockRequest) error 367 | 368 | // LockWait acquires a lock on a byte range of the node, waiting 369 | // until the lock can be obtained (or context is canceled). 370 | LockWait(ctx context.Context, req *fuse.LockWaitRequest) error 371 | 372 | // Unlock releases the lock on a byte range of the node. Locks can 373 | // be released also implicitly, see HandleFlockLocker and 374 | // HandlePOSIXLocker. 375 | Unlock(ctx context.Context, req *fuse.UnlockRequest) error 376 | 377 | // QueryLock returns the current state of locks held for the byte 378 | // range of the node. 379 | // 380 | // See QueryLockRequest for details on how to respond. 381 | // 382 | // To simplify implementing this method, resp.Lock is prefilled to 383 | // have Lock.Type F_UNLCK, and the whole struct should be 384 | // overwritten for in case of conflicting locks. 385 | QueryLock(ctx context.Context, req *fuse.QueryLockRequest, resp *fuse.QueryLockResponse) error 386 | } 387 | 388 | // HandleFlockLocker describes locking behavior unique to flock (BSD) 389 | // locks. See HandleLocker. 390 | type HandleFlockLocker interface { 391 | HandleLocker 392 | 393 | // Flock unlocking can also happen implicitly as part of Release, 394 | // in which case Unlock is not called, and Release will have 395 | // ReleaseFlags bit ReleaseFlockUnlock set. 396 | HandleReleaser 397 | } 398 | 399 | // HandlePOSIXLocker describes locking behavior unique to POSIX (fcntl 400 | // F_SETLK) locks. See HandleLocker. 401 | type HandlePOSIXLocker interface { 402 | HandleLocker 403 | 404 | // POSIX unlocking can also happen implicitly as part of Flush, 405 | // in which case Unlock is not called. 406 | HandleFlusher 407 | } 408 | 409 | type Config struct { 410 | // Function to send debug log messages to. If nil, use fuse.Debug. 411 | // Note that changing this or fuse.Debug may not affect existing 412 | // calls to Serve. 413 | // 414 | // See fuse.Debug for the rules that log functions must follow. 415 | Debug func(msg interface{}) 416 | 417 | // Function to put things into context for processing the request. 418 | // The returned context must have ctx as its parent. 419 | // 420 | // Note that changing this may not affect existing calls to Serve. 421 | // 422 | // Must not retain req. 423 | WithContext func(ctx context.Context, req fuse.Request) context.Context 424 | } 425 | 426 | // New returns a new FUSE server ready to serve this kernel FUSE 427 | // connection. 428 | // 429 | // Config may be nil. 430 | func New(conn *fuse.Conn, config *Config) *Server { 431 | s := &Server{ 432 | conn: conn, 433 | req: map[fuse.RequestID]func(){}, 434 | nodeRef: map[Node]fuse.NodeID{}, 435 | inode2id: map[uint64]fuse.NodeID{}, 436 | notifyWait: map[fuse.RequestID]chan<- *fuse.NotifyReply{}, 437 | dynamicInode: GenerateDynamicInode, 438 | } 439 | if config != nil { 440 | s.debug = config.Debug 441 | s.context = config.WithContext 442 | } 443 | if s.debug == nil { 444 | s.debug = fuse.Debug 445 | } 446 | return s 447 | } 448 | 449 | type Server struct { 450 | // set in New 451 | conn *fuse.Conn 452 | debug func(msg interface{}) 453 | context func(ctx context.Context, req fuse.Request) context.Context 454 | 455 | // set once at Serve time 456 | fs FS 457 | dynamicInode func(parent uint64, name string) uint64 458 | 459 | // state, protected by meta 460 | meta sync.Mutex 461 | req map[fuse.RequestID]func() // map request to cancel functions 462 | node []*serveNode 463 | nodeRefLock sync.Mutex 464 | nodeRef map[Node]fuse.NodeID 465 | inode2idLock sync.Mutex 466 | inode2id map[uint64]fuse.NodeID 467 | handle []*serveHandle 468 | freeNode []fuse.NodeID 469 | freeHandle []fuse.HandleID 470 | nodeGen uint64 471 | 472 | // pending notify upcalls to kernel 473 | notifyMu sync.Mutex 474 | notifySeq fuse.RequestID 475 | notifyWait map[fuse.RequestID]chan<- *fuse.NotifyReply 476 | 477 | // Used to ensure worker goroutines finish before Serve returns 478 | wg sync.WaitGroup 479 | } 480 | 481 | // Serve serves the FUSE connection by making calls to the methods 482 | // of fs and the Nodes and Handles it makes available. It returns only 483 | // when the connection has been closed or an unexpected error occurs. 484 | func (s *Server) Serve(fs FS) error { 485 | defer s.wg.Wait() // Wait for worker goroutines to complete before return 486 | 487 | s.fs = fs 488 | if dyn, ok := fs.(FSInodeGenerator); ok { 489 | s.dynamicInode = dyn.GenerateInode 490 | } 491 | 492 | root, err := fs.Root() 493 | if err != nil { 494 | return fmt.Errorf("cannot obtain root node: %v", err) 495 | } 496 | // Recognize the root node if it's ever returned from Lookup, 497 | // passed to Invalidate, etc. 498 | s.setNodeId(root, 1) 499 | s.node = append(s.node, nil, &serveNode{ 500 | inode: 1, 501 | generation: s.nodeGen, 502 | node: root, 503 | refs: 1, 504 | }) 505 | s.handle = append(s.handle, nil) 506 | 507 | for { 508 | req, err := s.conn.ReadRequest() 509 | if err != nil { 510 | if err == io.EOF { 511 | break 512 | } 513 | return err 514 | } 515 | 516 | go s.serve(req) 517 | 518 | } 519 | 520 | return nil 521 | } 522 | 523 | // Serve serves a FUSE connection with the default settings. See 524 | // Server.Serve. 525 | func Serve(c *fuse.Conn, fs FS) error { 526 | server := New(c, nil) 527 | return server.Serve(fs) 528 | } 529 | 530 | type nothing struct{} 531 | 532 | type serveNode struct { 533 | inode uint64 534 | generation uint64 535 | node Node 536 | refs uint64 537 | 538 | // Delay freeing the NodeID until waitgroup is done. This allows 539 | // using the NodeID for short periods of time without holding the 540 | // Server.meta lock. 541 | // 542 | // Rules: 543 | // 544 | // - hold Server.meta while calling wg.Add, then unlock 545 | // - do NOT try to reacquire Server.meta 546 | wg sync.WaitGroup 547 | } 548 | 549 | func (sn *serveNode) attr(ctx context.Context, attr *fuse.Attr) error { 550 | err := nodeAttr(ctx, sn.node, attr) 551 | if attr.Inode == 0 { 552 | attr.Inode = sn.inode 553 | } 554 | return err 555 | } 556 | 557 | type serveHandle struct { 558 | handle Handle 559 | readData []byte 560 | } 561 | 562 | func (c *Server) saveNode(inode uint64, node Node) (id fuse.NodeID, gen uint64) { 563 | c.meta.Lock() 564 | defer c.meta.Unlock() 565 | 566 | if id, ok := c.getNodeId(node); ok { 567 | sn := c.node[id] 568 | sn.refs++ 569 | return id, sn.generation 570 | } 571 | 572 | sn := &serveNode{inode: inode, node: node, refs: 1} 573 | if n := len(c.freeNode); n > 0 { 574 | id = c.freeNode[n-1] 575 | c.freeNode = c.freeNode[:n-1] 576 | c.node[id] = sn 577 | c.nodeGen++ 578 | } else { 579 | id = fuse.NodeID(len(c.node)) 580 | c.node = append(c.node, sn) 581 | } 582 | sn.generation = c.nodeGen 583 | c.setNodeId(node, id) 584 | return id, sn.generation 585 | } 586 | 587 | func (c *Server) saveHandle(handle Handle) (id fuse.HandleID) { 588 | c.meta.Lock() 589 | shandle := &serveHandle{handle: handle} 590 | if n := len(c.freeHandle); n > 0 { 591 | id = c.freeHandle[n-1] 592 | c.freeHandle = c.freeHandle[:n-1] 593 | c.handle[id] = shandle 594 | } else { 595 | id = fuse.HandleID(len(c.handle)) 596 | c.handle = append(c.handle, shandle) 597 | } 598 | c.meta.Unlock() 599 | return 600 | } 601 | 602 | type nodeRefcountDropBug struct { 603 | N uint64 604 | Refs uint64 605 | Node fuse.NodeID 606 | } 607 | 608 | func (n nodeRefcountDropBug) String() string { 609 | return fmt.Sprintf("bug: trying to drop %d of %d references to %v", n.N, n.Refs, n.Node) 610 | } 611 | 612 | // dropNode decreases reference count for node with id by n. 613 | // If reference count dropped to zero, returns true. 614 | // Note that node is not guaranteed to be non-nil. 615 | func (c *Server) dropNode(id fuse.NodeID, n uint64) (node Node, forget bool) { 616 | c.meta.Lock() 617 | defer c.meta.Unlock() 618 | snode := c.node[id] 619 | 620 | if snode == nil { 621 | // this should only happen if refcounts kernel<->us disagree 622 | // *and* two ForgetRequests for the same node race each other; 623 | // this indicates a bug somewhere 624 | c.debug(nodeRefcountDropBug{N: n, Node: id}) 625 | 626 | // we may end up triggering Forget twice, but that's better 627 | // than not even once, and that's the best we can do 628 | return nil, true 629 | } 630 | 631 | if n > snode.refs { 632 | c.debug(nodeRefcountDropBug{N: n, Refs: snode.refs, Node: id}) 633 | n = snode.refs 634 | } 635 | 636 | snode.refs -= n 637 | if snode.refs == 0 { 638 | snode.wg.Wait() 639 | c.node[id] = nil 640 | c.delNodeId(snode.node) 641 | c.freeNode = append(c.freeNode, id) 642 | return snode.node, true 643 | } 644 | return nil, false 645 | } 646 | 647 | func (c *Server) dropHandle(id fuse.HandleID) { 648 | c.meta.Lock() 649 | c.handle[id] = nil 650 | c.freeHandle = append(c.freeHandle, id) 651 | c.meta.Unlock() 652 | } 653 | 654 | type missingHandle struct { 655 | Handle fuse.HandleID 656 | MaxHandle fuse.HandleID 657 | } 658 | 659 | func (m missingHandle) String() string { 660 | return fmt.Sprint("missing handle: ", m.Handle, m.MaxHandle) 661 | } 662 | 663 | // Returns nil for invalid handles. 664 | func (c *Server) getHandle(id fuse.HandleID) (shandle *serveHandle) { 665 | c.meta.Lock() 666 | defer c.meta.Unlock() 667 | if id < fuse.HandleID(len(c.handle)) { 668 | shandle = c.handle[uint(id)] 669 | } 670 | if shandle == nil { 671 | c.debug(missingHandle{ 672 | Handle: id, 673 | MaxHandle: fuse.HandleID(len(c.handle)), 674 | }) 675 | } 676 | return 677 | } 678 | 679 | type request struct { 680 | In interface{} `json:",omitempty"` 681 | } 682 | 683 | func (r request) String() string { 684 | return fmt.Sprintf("<- %s", r.In) 685 | } 686 | 687 | type logResponseHeader struct { 688 | ID fuse.RequestID 689 | } 690 | 691 | func (m logResponseHeader) String() string { 692 | return fmt.Sprintf("ID=%v", m.ID) 693 | } 694 | 695 | type response struct { 696 | Op string 697 | Request logResponseHeader 698 | Out interface{} `json:",omitempty"` 699 | // Errno contains the errno value as a string, for example "EPERM". 700 | Errno string `json:",omitempty"` 701 | // Error may contain a free form error message. 702 | Error string `json:",omitempty"` 703 | } 704 | 705 | func (r response) errstr() string { 706 | s := r.Errno 707 | if r.Error != "" { 708 | // prefix the errno constant to the long form message 709 | s = s + ": " + r.Error 710 | } 711 | return s 712 | } 713 | 714 | func (r response) String() string { 715 | switch { 716 | case r.Errno != "" && r.Out != nil: 717 | return fmt.Sprintf("-> [%v] %v error=%s", r.Request, r.Out, r.errstr()) 718 | case r.Errno != "": 719 | return fmt.Sprintf("-> [%v] %s error=%s", r.Request, r.Op, r.errstr()) 720 | case r.Out != nil: 721 | // make sure (seemingly) empty values are readable 722 | switch r.Out.(type) { 723 | case string: 724 | return fmt.Sprintf("-> [%v] %s %q", r.Request, r.Op, r.Out) 725 | case []byte: 726 | return fmt.Sprintf("-> [%v] %s [% x]", r.Request, r.Op, r.Out) 727 | default: 728 | return fmt.Sprintf("-> [%v] %v", r.Request, r.Out) 729 | } 730 | default: 731 | return fmt.Sprintf("-> [%v] %s", r.Request, r.Op) 732 | } 733 | } 734 | 735 | type notification struct { 736 | Op string 737 | Node fuse.NodeID 738 | Out interface{} `json:",omitempty"` 739 | Err string `json:",omitempty"` 740 | } 741 | 742 | func (n notification) String() string { 743 | var buf bytes.Buffer 744 | fmt.Fprintf(&buf, "=> %s %v", n.Op, n.Node) 745 | if n.Out != nil { 746 | // make sure (seemingly) empty values are readable 747 | switch n.Out.(type) { 748 | case string: 749 | fmt.Fprintf(&buf, " %q", n.Out) 750 | case []byte: 751 | fmt.Fprintf(&buf, " [% x]", n.Out) 752 | default: 753 | fmt.Fprintf(&buf, " %s", n.Out) 754 | } 755 | } 756 | if n.Err != "" { 757 | fmt.Fprintf(&buf, " Err:%v", n.Err) 758 | } 759 | return buf.String() 760 | } 761 | 762 | type notificationRequest struct { 763 | ID fuse.RequestID 764 | Op string 765 | Node fuse.NodeID 766 | Out interface{} `json:",omitempty"` 767 | } 768 | 769 | func (n notificationRequest) String() string { 770 | var buf bytes.Buffer 771 | fmt.Fprintf(&buf, ">> %s [ID=%d] %v", n.Op, n.ID, n.Node) 772 | if n.Out != nil { 773 | // make sure (seemingly) empty values are readable 774 | switch n.Out.(type) { 775 | case string: 776 | fmt.Fprintf(&buf, " %q", n.Out) 777 | case []byte: 778 | fmt.Fprintf(&buf, " [% x]", n.Out) 779 | default: 780 | fmt.Fprintf(&buf, " %s", n.Out) 781 | } 782 | } 783 | return buf.String() 784 | } 785 | 786 | type notificationResponse struct { 787 | ID fuse.RequestID 788 | Op string 789 | In interface{} `json:",omitempty"` 790 | Err string `json:",omitempty"` 791 | } 792 | 793 | func (n notificationResponse) String() string { 794 | var buf bytes.Buffer 795 | fmt.Fprintf(&buf, "<< [ID=%d] %s", n.ID, n.Op) 796 | if n.In != nil { 797 | // make sure (seemingly) empty values are readable 798 | switch n.In.(type) { 799 | case string: 800 | fmt.Fprintf(&buf, " %q", n.In) 801 | case []byte: 802 | fmt.Fprintf(&buf, " [% x]", n.In) 803 | default: 804 | fmt.Fprintf(&buf, " %s", n.In) 805 | } 806 | } 807 | if n.Err != "" { 808 | fmt.Fprintf(&buf, " Err:%v", n.Err) 809 | } 810 | return buf.String() 811 | } 812 | 813 | type logMissingNode struct { 814 | MaxNode fuse.NodeID 815 | } 816 | 817 | func opName(req fuse.Request) string { 818 | t := reflect.Indirect(reflect.ValueOf(req)).Type() 819 | s := t.Name() 820 | s = strings.TrimSuffix(s, "Request") 821 | return s 822 | } 823 | 824 | type logLinkRequestOldNodeNotFound struct { 825 | Request *fuse.Header 826 | In *fuse.LinkRequest 827 | } 828 | 829 | func (m *logLinkRequestOldNodeNotFound) String() string { 830 | return fmt.Sprintf("In LinkRequest (request %v), node %d not found", m.Request.Hdr().ID, m.In.OldNode) 831 | } 832 | 833 | type renameNewDirNodeNotFound struct { 834 | Request *fuse.Header 835 | In *fuse.RenameRequest 836 | } 837 | 838 | func (m *renameNewDirNodeNotFound) String() string { 839 | return fmt.Sprintf("In RenameRequest (request %v), node %d not found", m.Request.Hdr().ID, m.In.NewDir) 840 | } 841 | 842 | type handlerPanickedError struct { 843 | Request interface{} 844 | Err interface{} 845 | } 846 | 847 | var _ error = handlerPanickedError{} 848 | 849 | func (h handlerPanickedError) Error() string { 850 | return fmt.Sprintf("handler panicked: %v", h.Err) 851 | } 852 | 853 | var _ fuse.ErrorNumber = handlerPanickedError{} 854 | 855 | func (h handlerPanickedError) Errno() fuse.Errno { 856 | if err, ok := h.Err.(fuse.ErrorNumber); ok { 857 | return err.Errno() 858 | } 859 | return fuse.DefaultErrno 860 | } 861 | 862 | // handlerTerminatedError happens when a handler terminates itself 863 | // with runtime.Goexit. This is most commonly because of incorrect use 864 | // of testing.TB.FailNow, typically via t.Fatal. 865 | type handlerTerminatedError struct { 866 | Request interface{} 867 | } 868 | 869 | var _ error = handlerTerminatedError{} 870 | 871 | func (h handlerTerminatedError) Error() string { 872 | return fmt.Sprintf("handler terminated (called runtime.Goexit)") 873 | } 874 | 875 | var _ fuse.ErrorNumber = handlerTerminatedError{} 876 | 877 | func (h handlerTerminatedError) Errno() fuse.Errno { 878 | return fuse.DefaultErrno 879 | } 880 | 881 | type handleNotReaderError struct { 882 | handle Handle 883 | } 884 | 885 | var _ error = handleNotReaderError{} 886 | 887 | func (e handleNotReaderError) Error() string { 888 | return fmt.Sprintf("handle has no Read: %T", e.handle) 889 | } 890 | 891 | var _ fuse.ErrorNumber = handleNotReaderError{} 892 | 893 | func (e handleNotReaderError) Errno() fuse.Errno { 894 | return fuse.Errno(syscall.ENOTSUP) 895 | } 896 | 897 | func initLookupResponse(s *fuse.LookupResponse) { 898 | s.EntryValid = entryValidTime 899 | } 900 | 901 | type logDuplicateRequestID struct { 902 | New fuse.Request 903 | Old fuse.Request 904 | } 905 | 906 | func (m *logDuplicateRequestID) String() string { 907 | return fmt.Sprintf("Duplicate request: new %v, old %v", m.New, m.Old) 908 | } 909 | 910 | func (c *Server) serve(r fuse.Request) { 911 | ctx, cancel := context.WithCancel(context.Background()) 912 | defer cancel() 913 | parentCtx := ctx 914 | if c.context != nil { 915 | ctx = c.context(ctx, r) 916 | } 917 | 918 | switch r.(type) { 919 | case *fuse.NotifyReply: 920 | // don't log NotifyReply here, they're logged by the recipient 921 | // as soon as we have decoded them to the right types 922 | default: 923 | c.debug(request{ 924 | In: r, 925 | }) 926 | } 927 | var node Node 928 | var snode *serveNode 929 | c.meta.Lock() 930 | hdr := r.Hdr() 931 | if id := hdr.Node; id != 0 { 932 | if id < fuse.NodeID(len(c.node)) { 933 | snode = c.node[uint(id)] 934 | } 935 | if snode == nil { 936 | c.meta.Unlock() 937 | err := syscall.ESTALE 938 | c.debug(response{ 939 | Op: opName(r), 940 | Request: logResponseHeader{ID: hdr.ID}, 941 | Error: fuse.Errno(err).ErrnoName(), 942 | // this is the only place that sets both Error and 943 | // Out; not sure if i want to do that; might get rid 944 | // of len(c.node) things altogether 945 | Out: logMissingNode{ 946 | MaxNode: fuse.NodeID(len(c.node)), 947 | }, 948 | }) 949 | r.RespondError(err) 950 | return 951 | } 952 | node = snode.node 953 | } 954 | if c.req[hdr.ID] != nil { 955 | // This happens with OSXFUSE. Assume it's okay and 956 | // that we'll never see an interrupt for this one. 957 | // Otherwise everything wedges. TODO: Report to OSXFUSE? 958 | // 959 | // TODO this might have been because of missing done() calls 960 | } else { 961 | c.req[hdr.ID] = cancel 962 | } 963 | c.meta.Unlock() 964 | 965 | // Call this before responding. 966 | // After responding is too late: we might get another request 967 | // with the same ID and be very confused. 968 | done := func(resp interface{}) { 969 | msg := response{ 970 | Op: opName(r), 971 | Request: logResponseHeader{ID: hdr.ID}, 972 | } 973 | if err, ok := resp.(error); ok { 974 | errno := fuse.ToErrno(err) 975 | msg.Errno = errno.ErrnoName() 976 | if errno != err && syscall.Errno(errno) != err { 977 | // if it's more than just a fuse.Errno or a 978 | // syscall.Errno, log extra detail 979 | msg.Error = err.Error() 980 | } 981 | } else { 982 | msg.Out = resp 983 | } 984 | c.debug(msg) 985 | 986 | c.meta.Lock() 987 | delete(c.req, hdr.ID) 988 | c.meta.Unlock() 989 | } 990 | 991 | var responded bool 992 | defer func() { 993 | if rec := recover(); rec != nil { 994 | const size = 1 << 16 995 | buf := make([]byte, size) 996 | n := runtime.Stack(buf, false) 997 | buf = buf[:n] 998 | log.Printf("fuse: panic in handler for %v: %v\n%s", r, rec, buf) 999 | err := handlerPanickedError{ 1000 | Request: r, 1001 | Err: rec, 1002 | } 1003 | done(err) 1004 | r.RespondError(err) 1005 | return 1006 | } 1007 | 1008 | if !responded { 1009 | err := handlerTerminatedError{ 1010 | Request: r, 1011 | } 1012 | done(err) 1013 | r.RespondError(err) 1014 | } 1015 | }() 1016 | 1017 | if err := c.handleRequest(ctx, node, snode, r, done); err != nil { 1018 | if err == context.Canceled { 1019 | select { 1020 | case <-parentCtx.Done(): 1021 | // We canceled the parent context because of an 1022 | // incoming interrupt request, so return EINTR 1023 | // to trigger the right behavior in the client app. 1024 | // 1025 | // Only do this when it's the parent context that was 1026 | // canceled, not a context controlled by the program 1027 | // using this library, so we don't return EINTR too 1028 | // eagerly -- it might cause busy loops. 1029 | // 1030 | // Decent write-up on role of EINTR: 1031 | // http://250bpm.com/blog:12 1032 | err = syscall.EINTR 1033 | default: 1034 | // nothing 1035 | } 1036 | } 1037 | done(err) 1038 | r.RespondError(err) 1039 | } 1040 | 1041 | // disarm runtime.Goexit protection 1042 | responded = true 1043 | } 1044 | 1045 | // handleRequest will either a) call done(s) and r.Respond(s) OR b) return an error. 1046 | func (c *Server) handleRequest(ctx context.Context, node Node, snode *serveNode, r fuse.Request, done func(resp interface{})) error { 1047 | switch r := r.(type) { 1048 | default: 1049 | // Note: To FUSE, ENOSYS means "this server never implements this request." 1050 | // It would be inappropriate to return ENOSYS for other operations in this 1051 | // switch that might only be unavailable in some contexts, not all. 1052 | return syscall.ENOSYS 1053 | 1054 | case *fuse.StatfsRequest: 1055 | s := &fuse.StatfsResponse{} 1056 | if fs, ok := c.fs.(FSStatfser); ok { 1057 | if err := fs.Statfs(ctx, r, s); err != nil { 1058 | return err 1059 | } 1060 | } 1061 | done(s) 1062 | r.Respond(s) 1063 | return nil 1064 | 1065 | // Node operations. 1066 | case *fuse.GetattrRequest: 1067 | s := &fuse.GetattrResponse{} 1068 | if n, ok := node.(NodeGetattrer); ok { 1069 | if err := n.Getattr(ctx, r, s); err != nil { 1070 | return err 1071 | } 1072 | } else { 1073 | if err := snode.attr(ctx, &s.Attr); err != nil { 1074 | return err 1075 | } 1076 | } 1077 | done(s) 1078 | r.Respond(s) 1079 | return nil 1080 | 1081 | case *fuse.SetattrRequest: 1082 | s := &fuse.SetattrResponse{} 1083 | if n, ok := node.(NodeSetattrer); ok { 1084 | if err := n.Setattr(ctx, r, s); err != nil { 1085 | return err 1086 | } 1087 | } 1088 | 1089 | if err := snode.attr(ctx, &s.Attr); err != nil { 1090 | return err 1091 | } 1092 | done(s) 1093 | r.Respond(s) 1094 | return nil 1095 | 1096 | case *fuse.SymlinkRequest: 1097 | s := &fuse.SymlinkResponse{} 1098 | initLookupResponse(&s.LookupResponse) 1099 | n, ok := node.(NodeSymlinker) 1100 | if !ok { 1101 | return syscall.EIO // XXX or EPERM like Mkdir? 1102 | } 1103 | n2, err := n.Symlink(ctx, r) 1104 | if err != nil { 1105 | return err 1106 | } 1107 | if err := c.saveLookup(ctx, &s.LookupResponse, snode, r.NewName, n2); err != nil { 1108 | return err 1109 | } 1110 | done(s) 1111 | r.Respond(s) 1112 | return nil 1113 | 1114 | case *fuse.ReadlinkRequest: 1115 | n, ok := node.(NodeReadlinker) 1116 | if !ok { 1117 | return syscall.EIO /// XXX or EPERM? 1118 | } 1119 | target, err := n.Readlink(ctx, r) 1120 | if err != nil { 1121 | return err 1122 | } 1123 | done(target) 1124 | r.Respond(target) 1125 | return nil 1126 | 1127 | case *fuse.LinkRequest: 1128 | n, ok := node.(NodeLinker) 1129 | if !ok { 1130 | return syscall.EIO /// XXX or EPERM? 1131 | } 1132 | c.meta.Lock() 1133 | var oldNode *serveNode 1134 | if int(r.OldNode) < len(c.node) { 1135 | oldNode = c.node[r.OldNode] 1136 | } 1137 | c.meta.Unlock() 1138 | if oldNode == nil { 1139 | c.debug(logLinkRequestOldNodeNotFound{ 1140 | Request: r.Hdr(), 1141 | In: r, 1142 | }) 1143 | return syscall.EIO 1144 | } 1145 | n2, err := n.Link(ctx, r, oldNode.node) 1146 | if err != nil { 1147 | return err 1148 | } 1149 | s := &fuse.LookupResponse{} 1150 | initLookupResponse(s) 1151 | if err := c.saveLookup(ctx, s, snode, r.NewName, n2); err != nil { 1152 | return err 1153 | } 1154 | done(s) 1155 | r.Respond(s) 1156 | return nil 1157 | 1158 | case *fuse.RemoveRequest: 1159 | n, ok := node.(NodeRemover) 1160 | if !ok { 1161 | return syscall.EIO /// XXX or EPERM? 1162 | } 1163 | err := n.Remove(ctx, r) 1164 | if err != nil { 1165 | return err 1166 | } 1167 | done(nil) 1168 | r.Respond() 1169 | return nil 1170 | 1171 | case *fuse.AccessRequest: 1172 | if n, ok := node.(NodeAccesser); ok { 1173 | if err := n.Access(ctx, r); err != nil { 1174 | return err 1175 | } 1176 | } 1177 | done(nil) 1178 | r.Respond() 1179 | return nil 1180 | 1181 | case *fuse.LookupRequest: 1182 | var n2 Node 1183 | var err error 1184 | s := &fuse.LookupResponse{} 1185 | initLookupResponse(s) 1186 | if n, ok := node.(NodeStringLookuper); ok { 1187 | n2, err = n.Lookup(ctx, r.Name) 1188 | } else if n, ok := node.(NodeRequestLookuper); ok { 1189 | n2, err = n.Lookup(ctx, r, s) 1190 | } else { 1191 | return syscall.ENOENT 1192 | } 1193 | if err != nil { 1194 | return err 1195 | } 1196 | if err := c.saveLookup(ctx, s, snode, r.Name, n2); err != nil { 1197 | return err 1198 | } 1199 | done(s) 1200 | r.Respond(s) 1201 | return nil 1202 | 1203 | case *fuse.MkdirRequest: 1204 | s := &fuse.MkdirResponse{} 1205 | initLookupResponse(&s.LookupResponse) 1206 | n, ok := node.(NodeMkdirer) 1207 | if !ok { 1208 | return syscall.EPERM 1209 | } 1210 | n2, err := n.Mkdir(ctx, r) 1211 | if err != nil { 1212 | return err 1213 | } 1214 | if err := c.saveLookup(ctx, &s.LookupResponse, snode, r.Name, n2); err != nil { 1215 | return err 1216 | } 1217 | done(s) 1218 | r.Respond(s) 1219 | return nil 1220 | 1221 | case *fuse.OpenRequest: 1222 | s := &fuse.OpenResponse{} 1223 | var h2 Handle 1224 | if n, ok := node.(NodeOpener); ok { 1225 | hh, err := n.Open(ctx, r, s) 1226 | if err != nil { 1227 | return err 1228 | } 1229 | h2 = hh 1230 | } else { 1231 | h2 = node 1232 | } 1233 | s.Handle = c.saveHandle(h2) 1234 | done(s) 1235 | r.Respond(s) 1236 | return nil 1237 | 1238 | case *fuse.CreateRequest: 1239 | n, ok := node.(NodeCreater) 1240 | if !ok { 1241 | // If we send back ENOSYS, FUSE will try mknod+open. 1242 | return syscall.EPERM 1243 | } 1244 | s := &fuse.CreateResponse{OpenResponse: fuse.OpenResponse{}} 1245 | initLookupResponse(&s.LookupResponse) 1246 | n2, h2, err := n.Create(ctx, r, s) 1247 | if err != nil { 1248 | return err 1249 | } 1250 | if err := c.saveLookup(ctx, &s.LookupResponse, snode, r.Name, n2); err != nil { 1251 | return err 1252 | } 1253 | s.Handle = c.saveHandle(h2) 1254 | done(s) 1255 | r.Respond(s) 1256 | return nil 1257 | 1258 | case *fuse.GetxattrRequest: 1259 | n, ok := node.(NodeGetxattrer) 1260 | if !ok { 1261 | return syscall.ENOTSUP 1262 | } 1263 | s := &fuse.GetxattrResponse{} 1264 | err := n.Getxattr(ctx, r, s) 1265 | if err != nil { 1266 | return err 1267 | } 1268 | if r.Size != 0 && uint64(len(s.Xattr)) > uint64(r.Size) { 1269 | return syscall.ERANGE 1270 | } 1271 | done(s) 1272 | r.Respond(s) 1273 | return nil 1274 | 1275 | case *fuse.ListxattrRequest: 1276 | n, ok := node.(NodeListxattrer) 1277 | if !ok { 1278 | return syscall.ENOTSUP 1279 | } 1280 | s := &fuse.ListxattrResponse{} 1281 | err := n.Listxattr(ctx, r, s) 1282 | if err != nil { 1283 | return err 1284 | } 1285 | if r.Size != 0 && uint64(len(s.Xattr)) > uint64(r.Size) { 1286 | return syscall.ERANGE 1287 | } 1288 | done(s) 1289 | r.Respond(s) 1290 | return nil 1291 | 1292 | case *fuse.SetxattrRequest: 1293 | n, ok := node.(NodeSetxattrer) 1294 | if !ok { 1295 | return syscall.ENOTSUP 1296 | } 1297 | err := n.Setxattr(ctx, r) 1298 | if err != nil { 1299 | return err 1300 | } 1301 | done(nil) 1302 | r.Respond() 1303 | return nil 1304 | 1305 | case *fuse.RemovexattrRequest: 1306 | n, ok := node.(NodeRemovexattrer) 1307 | if !ok { 1308 | return syscall.ENOTSUP 1309 | } 1310 | err := n.Removexattr(ctx, r) 1311 | if err != nil { 1312 | return err 1313 | } 1314 | done(nil) 1315 | r.Respond() 1316 | return nil 1317 | 1318 | case *fuse.ForgetRequest: 1319 | _, forget := c.dropNode(r.Hdr().Node, r.N) 1320 | if forget { 1321 | n, ok := node.(NodeForgetter) 1322 | if ok { 1323 | n.Forget() 1324 | } 1325 | } 1326 | done(nil) 1327 | r.Respond() 1328 | return nil 1329 | 1330 | case *fuse.BatchForgetRequest: 1331 | // BatchForgetRequest is hard to unit test, as it 1332 | // fundamentally relies on something unprivileged userspace 1333 | // has little control over. A root-only, Linux-only test could 1334 | // be written with `echo 2 >/proc/sys/vm/drop_caches`, but 1335 | // that would still rely on timing, the number of batches and 1336 | // operation spread over them could vary, it wouldn't run in a 1337 | // typical container regardless of privileges, and it would 1338 | // degrade performance for the rest of the machine. It would 1339 | // still probably be worth doing, just not the most fun. 1340 | 1341 | // node is nil here because BatchForget as a message is not 1342 | // aimed at a any one node 1343 | for _, item := range r.Forget { 1344 | node, forget := c.dropNode(item.NodeID, item.N) 1345 | // node can be nil here if kernel vs our refcount were out 1346 | // of sync and multiple Forgets raced each other 1347 | if node == nil { 1348 | // nothing we can do about that 1349 | continue 1350 | } 1351 | if forget { 1352 | n, ok := node.(NodeForgetter) 1353 | if ok { 1354 | n.Forget() 1355 | } 1356 | } 1357 | } 1358 | done(nil) 1359 | r.Respond() 1360 | return nil 1361 | 1362 | // Handle operations. 1363 | case *fuse.ReadRequest: 1364 | shandle := c.getHandle(r.Handle) 1365 | if shandle == nil { 1366 | return syscall.ESTALE 1367 | } 1368 | handle := shandle.handle 1369 | 1370 | s := &fuse.ReadResponse{Data: make([]byte, 0, r.Size)} 1371 | if r.Dir { 1372 | if h, ok := handle.(HandleReadDirAller); ok { 1373 | // detect rewinddir(3) or similar seek and refresh 1374 | // contents 1375 | if r.Offset == 0 { 1376 | shandle.readData = nil 1377 | } 1378 | 1379 | if shandle.readData == nil { 1380 | dirs, err := h.ReadDirAll(ctx) 1381 | if err != nil { 1382 | return err 1383 | } 1384 | var data []byte 1385 | for _, dir := range dirs { 1386 | if dir.Inode == 0 { 1387 | dir.Inode = c.dynamicInode(snode.inode, dir.Name) 1388 | } 1389 | data = fuse.AppendDirent(data, dir) 1390 | } 1391 | shandle.readData = data 1392 | } 1393 | fuseutil.HandleRead(r, s, shandle.readData) 1394 | done(s) 1395 | r.Respond(s) 1396 | return nil 1397 | } 1398 | } else { 1399 | if h, ok := handle.(HandleReadAller); ok { 1400 | if shandle.readData == nil { 1401 | data, err := h.ReadAll(ctx) 1402 | if err != nil { 1403 | return err 1404 | } 1405 | if data == nil { 1406 | data = []byte{} 1407 | } 1408 | shandle.readData = data 1409 | } 1410 | fuseutil.HandleRead(r, s, shandle.readData) 1411 | done(s) 1412 | r.Respond(s) 1413 | return nil 1414 | } 1415 | h, ok := handle.(HandleReader) 1416 | if !ok { 1417 | err := handleNotReaderError{handle: handle} 1418 | return err 1419 | } 1420 | if err := h.Read(ctx, r, s); err != nil { 1421 | return err 1422 | } 1423 | } 1424 | done(s) 1425 | r.Respond(s) 1426 | return nil 1427 | 1428 | case *fuse.WriteRequest: 1429 | shandle := c.getHandle(r.Handle) 1430 | if shandle == nil { 1431 | return syscall.ESTALE 1432 | } 1433 | 1434 | s := &fuse.WriteResponse{} 1435 | if h, ok := shandle.handle.(HandleWriter); ok { 1436 | if err := h.Write(ctx, r, s); err != nil { 1437 | return err 1438 | } 1439 | done(s) 1440 | r.Respond(s) 1441 | return nil 1442 | } 1443 | return syscall.EIO 1444 | 1445 | case *fuse.FlushRequest: 1446 | shandle := c.getHandle(r.Handle) 1447 | if shandle == nil { 1448 | return syscall.ESTALE 1449 | } 1450 | handle := shandle.handle 1451 | 1452 | if h, ok := handle.(HandleFlusher); ok { 1453 | if err := h.Flush(ctx, r); err != nil { 1454 | return err 1455 | } 1456 | } 1457 | done(nil) 1458 | r.Respond() 1459 | return nil 1460 | 1461 | case *fuse.ReleaseRequest: 1462 | shandle := c.getHandle(r.Handle) 1463 | if shandle == nil { 1464 | return syscall.ESTALE 1465 | } 1466 | handle := shandle.handle 1467 | 1468 | // No matter what, release the handle. 1469 | c.dropHandle(r.Handle) 1470 | 1471 | if h, ok := handle.(HandleReleaser); ok { 1472 | if err := h.Release(ctx, r); err != nil { 1473 | return err 1474 | } 1475 | } 1476 | done(nil) 1477 | r.Respond() 1478 | return nil 1479 | 1480 | case *fuse.DestroyRequest: 1481 | if fs, ok := c.fs.(FSDestroyer); ok { 1482 | fs.Destroy() 1483 | } 1484 | done(nil) 1485 | r.Respond() 1486 | return nil 1487 | 1488 | case *fuse.RenameRequest: 1489 | c.meta.Lock() 1490 | var newDirNode *serveNode 1491 | if int(r.NewDir) < len(c.node) { 1492 | newDirNode = c.node[r.NewDir] 1493 | } 1494 | c.meta.Unlock() 1495 | if newDirNode == nil { 1496 | c.debug(renameNewDirNodeNotFound{ 1497 | Request: r.Hdr(), 1498 | In: r, 1499 | }) 1500 | return syscall.EIO 1501 | } 1502 | n, ok := node.(NodeRenamer) 1503 | if !ok { 1504 | return syscall.EIO // XXX or EPERM like Mkdir? 1505 | } 1506 | err := n.Rename(ctx, r, newDirNode.node) 1507 | if err != nil { 1508 | return err 1509 | } 1510 | done(nil) 1511 | r.Respond() 1512 | return nil 1513 | 1514 | case *fuse.MknodRequest: 1515 | n, ok := node.(NodeMknoder) 1516 | if !ok { 1517 | return syscall.EIO 1518 | } 1519 | n2, err := n.Mknod(ctx, r) 1520 | if err != nil { 1521 | return err 1522 | } 1523 | s := &fuse.LookupResponse{} 1524 | initLookupResponse(s) 1525 | if err := c.saveLookup(ctx, s, snode, r.Name, n2); err != nil { 1526 | return err 1527 | } 1528 | done(s) 1529 | r.Respond(s) 1530 | return nil 1531 | 1532 | case *fuse.FsyncRequest: 1533 | n, ok := node.(NodeFsyncer) 1534 | if !ok { 1535 | return syscall.EIO 1536 | } 1537 | err := n.Fsync(ctx, r) 1538 | if err != nil { 1539 | return err 1540 | } 1541 | done(nil) 1542 | r.Respond() 1543 | return nil 1544 | 1545 | case *fuse.InterruptRequest: 1546 | c.meta.Lock() 1547 | if cancel := c.req[r.IntrID]; cancel != nil { 1548 | cancel() 1549 | delete(c.req, r.IntrID) 1550 | } 1551 | c.meta.Unlock() 1552 | done(nil) 1553 | r.Respond() 1554 | return nil 1555 | 1556 | case *fuse.PollRequest: 1557 | shandle := c.getHandle(r.Handle) 1558 | if shandle == nil { 1559 | return syscall.ESTALE 1560 | } 1561 | s := &fuse.PollResponse{} 1562 | 1563 | if h, ok := shandle.handle.(HandlePoller); ok { 1564 | if err := h.Poll(ctx, r, s); err != nil { 1565 | return err 1566 | } 1567 | done(s) 1568 | r.Respond(s) 1569 | return nil 1570 | } 1571 | 1572 | if n, ok := node.(NodePoller); ok { 1573 | if err := n.Poll(ctx, r, s); err != nil { 1574 | return err 1575 | } 1576 | done(s) 1577 | r.Respond(s) 1578 | return nil 1579 | } 1580 | 1581 | // fallback to always claim ready 1582 | s.REvents = fuse.DefaultPollMask 1583 | done(s) 1584 | r.Respond(s) 1585 | return nil 1586 | 1587 | case *fuse.NotifyReply: 1588 | c.notifyMu.Lock() 1589 | w, ok := c.notifyWait[r.Hdr().ID] 1590 | if ok { 1591 | delete(c.notifyWait, r.Hdr().ID) 1592 | } 1593 | c.notifyMu.Unlock() 1594 | if !ok { 1595 | c.debug(notificationResponse{ 1596 | ID: r.Hdr().ID, 1597 | Op: "NotifyReply", 1598 | Err: "unknown ID", 1599 | }) 1600 | return nil 1601 | } 1602 | w <- r 1603 | return nil 1604 | 1605 | case *fuse.LockRequest: 1606 | shandle := c.getHandle(r.Handle) 1607 | if shandle == nil { 1608 | return syscall.ESTALE 1609 | } 1610 | h, ok := shandle.handle.(HandleLocker) 1611 | if !ok { 1612 | return syscall.ENOTSUP 1613 | } 1614 | if err := h.Lock(ctx, r); err != nil { 1615 | return err 1616 | } 1617 | done(nil) 1618 | r.Respond() 1619 | return nil 1620 | 1621 | case *fuse.LockWaitRequest: 1622 | shandle := c.getHandle(r.Handle) 1623 | if shandle == nil { 1624 | return syscall.ESTALE 1625 | } 1626 | h, ok := shandle.handle.(HandleLocker) 1627 | if !ok { 1628 | return syscall.ENOTSUP 1629 | } 1630 | if err := h.LockWait(ctx, r); err != nil { 1631 | return err 1632 | } 1633 | done(nil) 1634 | r.Respond() 1635 | return nil 1636 | 1637 | case *fuse.UnlockRequest: 1638 | shandle := c.getHandle(r.Handle) 1639 | if shandle == nil { 1640 | return syscall.ESTALE 1641 | } 1642 | h, ok := shandle.handle.(HandleLocker) 1643 | if !ok { 1644 | return syscall.ENOTSUP 1645 | } 1646 | if err := h.Unlock(ctx, r); err != nil { 1647 | return err 1648 | } 1649 | done(nil) 1650 | r.Respond() 1651 | return nil 1652 | 1653 | case *fuse.QueryLockRequest: 1654 | shandle := c.getHandle(r.Handle) 1655 | if shandle == nil { 1656 | return syscall.ESTALE 1657 | } 1658 | h, ok := shandle.handle.(HandleLocker) 1659 | if !ok { 1660 | return syscall.ENOTSUP 1661 | } 1662 | s := &fuse.QueryLockResponse{ 1663 | Lock: fuse.FileLock{ 1664 | Type: unix.F_UNLCK, 1665 | }, 1666 | } 1667 | if err := h.QueryLock(ctx, r, s); err != nil { 1668 | return err 1669 | } 1670 | done(s) 1671 | r.Respond(s) 1672 | return nil 1673 | 1674 | /* case *FsyncdirRequest: 1675 | return ENOSYS 1676 | 1677 | case *BmapRequest: 1678 | return ENOSYS 1679 | */ 1680 | } 1681 | 1682 | panic("not reached") 1683 | } 1684 | 1685 | func (c *Server) saveLookup(ctx context.Context, s *fuse.LookupResponse, snode *serveNode, elem string, n2 Node) error { 1686 | if err := nodeAttr(ctx, n2, &s.Attr); err != nil { 1687 | return err 1688 | } 1689 | if s.Attr.Inode == 0 { 1690 | s.Attr.Inode = c.dynamicInode(snode.inode, elem) 1691 | } 1692 | 1693 | s.Node, s.Generation = c.saveNode(s.Attr.Inode, n2) 1694 | return nil 1695 | } 1696 | 1697 | type invalidateNodeDetail struct { 1698 | Off int64 1699 | Size int64 1700 | } 1701 | 1702 | func (i invalidateNodeDetail) String() string { 1703 | return fmt.Sprintf("Off:%d Size:%d", i.Off, i.Size) 1704 | } 1705 | 1706 | func errstr(err error) string { 1707 | if err == nil { 1708 | return "" 1709 | } 1710 | return err.Error() 1711 | } 1712 | 1713 | func (s *Server) getNodeId(node Node) (id fuse.NodeID, ok bool) { 1714 | if nodeIdentfier, isIdentifier := node.(NodeIdentifier); isIdentifier { 1715 | s.inode2idLock.Lock() 1716 | id, ok = s.inode2id[nodeIdentfier.Id()] 1717 | s.inode2idLock.Unlock() 1718 | return 1719 | } 1720 | s.nodeRefLock.Lock() 1721 | defer s.nodeRefLock.Unlock() 1722 | id, ok = s.nodeRef[node] 1723 | return 1724 | } 1725 | func (s *Server) setNodeId(node Node, id fuse.NodeID) { 1726 | if nodeIdentfier, ok := node.(NodeIdentifier); ok { 1727 | s.inode2idLock.Lock() 1728 | s.inode2id[nodeIdentfier.Id()] = id 1729 | s.inode2idLock.Unlock() 1730 | return 1731 | } 1732 | s.nodeRefLock.Lock() 1733 | defer s.nodeRefLock.Unlock() 1734 | s.nodeRef[node] = id 1735 | } 1736 | func (s *Server) delNodeId(node Node) { 1737 | if nodeIdentfier, ok := node.(NodeIdentifier); ok { 1738 | s.inode2idLock.Lock() 1739 | delete(s.inode2id, nodeIdentfier.Id()) 1740 | s.inode2idLock.Unlock() 1741 | return 1742 | } 1743 | s.nodeRefLock.Lock() 1744 | defer s.nodeRefLock.Unlock() 1745 | delete(s.nodeRef, node) 1746 | } 1747 | 1748 | func (s *Server) InvalidateInternalNode(oldNode Node, newNode Node, callbackFn func(internalNode Node)) { 1749 | id, ok := s.getNodeId(oldNode) 1750 | if ok { 1751 | s.meta.Lock() 1752 | snode := s.node[id] 1753 | s.meta.Unlock() 1754 | callbackFn(snode.node) 1755 | s.setNodeId(newNode, id) 1756 | s.delNodeId(oldNode) 1757 | } 1758 | } 1759 | 1760 | func (s *Server) FindInternalNode(nodeIdentifier Node) (internalNode Node, found bool) { 1761 | id, ok := s.getNodeId(nodeIdentifier) 1762 | if ok { 1763 | s.meta.Lock() 1764 | snode := s.node[id] 1765 | s.meta.Unlock() 1766 | return snode.node, true 1767 | } 1768 | return nil, false 1769 | } 1770 | 1771 | func (s *Server) invalidateNode(node Node, off int64, size int64) error { 1772 | id, ok := s.getNodeId(node) 1773 | if ok { 1774 | s.meta.Lock() 1775 | snode := s.node[id] 1776 | s.meta.Unlock() 1777 | if snode == nil { 1778 | return fuse.ErrNotCached 1779 | } 1780 | snode.wg.Add(1) 1781 | defer snode.wg.Done() 1782 | } 1783 | if !ok { 1784 | // This is what the kernel would have said, if we had been 1785 | // able to send this message; it's not cached. 1786 | return fuse.ErrNotCached 1787 | } 1788 | // Delay logging until after we can record the error too. We 1789 | // consider a /dev/fuse write to be instantaneous enough to not 1790 | // need separate before and after messages. 1791 | err := s.conn.InvalidateNode(id, off, size) 1792 | s.debug(notification{ 1793 | Op: "InvalidateNode", 1794 | Node: id, 1795 | Out: invalidateNodeDetail{ 1796 | Off: off, 1797 | Size: size, 1798 | }, 1799 | Err: errstr(err), 1800 | }) 1801 | return err 1802 | } 1803 | 1804 | // InvalidateNodeAttr invalidates the kernel cache of the attributes 1805 | // of node. 1806 | // 1807 | // Returns fuse.ErrNotCached if the kernel is not currently caching 1808 | // the node. 1809 | func (s *Server) InvalidateNodeAttr(node Node) error { 1810 | return s.invalidateNode(node, 0, 0) 1811 | } 1812 | 1813 | // InvalidateNodeData invalidates the kernel cache of the attributes 1814 | // and data of node. 1815 | // 1816 | // Returns fuse.ErrNotCached if the kernel is not currently caching 1817 | // the node. 1818 | func (s *Server) InvalidateNodeData(node Node) error { 1819 | return s.invalidateNode(node, 0, -1) 1820 | } 1821 | 1822 | // InvalidateNodeDataRange invalidates the kernel cache of the 1823 | // attributes and a range of the data of node. 1824 | // 1825 | // Returns fuse.ErrNotCached if the kernel is not currently caching 1826 | // the node. 1827 | func (s *Server) InvalidateNodeDataRange(node Node, off int64, size int64) error { 1828 | return s.invalidateNode(node, off, size) 1829 | } 1830 | 1831 | type invalidateEntryDetail struct { 1832 | Name string 1833 | } 1834 | 1835 | func (i invalidateEntryDetail) String() string { 1836 | return fmt.Sprintf("%q", i.Name) 1837 | } 1838 | 1839 | // InvalidateEntry invalidates the kernel cache of the directory entry 1840 | // identified by parent node and entry basename. 1841 | // 1842 | // Kernel may or may not cache directory listings. To invalidate 1843 | // those, use InvalidateNode to invalidate all of the data for a 1844 | // directory. (As of 2015-06, Linux FUSE does not cache directory 1845 | // listings.) 1846 | // 1847 | // Returns ErrNotCached if the kernel is not currently caching the 1848 | // node. 1849 | func (s *Server) InvalidateEntry(parent Node, name string) error { 1850 | id, ok := s.getNodeId(parent) 1851 | if ok { 1852 | s.meta.Lock() 1853 | snode := s.node[id] 1854 | s.meta.Unlock() 1855 | if snode == nil { 1856 | return fuse.ErrNotCached 1857 | } 1858 | snode.wg.Add(1) 1859 | defer snode.wg.Done() 1860 | } 1861 | if !ok { 1862 | // This is what the kernel would have said, if we had been 1863 | // able to send this message; it's not cached. 1864 | return fuse.ErrNotCached 1865 | } 1866 | err := s.conn.InvalidateEntry(id, name) 1867 | s.debug(notification{ 1868 | Op: "InvalidateEntry", 1869 | Node: id, 1870 | Out: invalidateEntryDetail{ 1871 | Name: name, 1872 | }, 1873 | Err: errstr(err), 1874 | }) 1875 | return err 1876 | } 1877 | 1878 | type notifyStoreRetrieveDetail struct { 1879 | Off uint64 1880 | Size uint64 1881 | } 1882 | 1883 | func (i notifyStoreRetrieveDetail) String() string { 1884 | return fmt.Sprintf("Off:%d Size:%d", i.Off, i.Size) 1885 | } 1886 | 1887 | type notifyRetrieveReplyDetail struct { 1888 | Size uint64 1889 | } 1890 | 1891 | func (i notifyRetrieveReplyDetail) String() string { 1892 | return fmt.Sprintf("Size:%d", i.Size) 1893 | } 1894 | 1895 | // NotifyStore puts data into the kernel page cache. 1896 | // 1897 | // Returns fuse.ErrNotCached if the kernel is not currently caching 1898 | // the node. 1899 | func (s *Server) NotifyStore(node Node, offset uint64, data []byte) error { 1900 | s.meta.Lock() 1901 | id, ok := s.getNodeId(node) 1902 | if ok { 1903 | snode := s.node[id] 1904 | snode.wg.Add(1) 1905 | defer snode.wg.Done() 1906 | } 1907 | s.meta.Unlock() 1908 | if !ok { 1909 | // This is what the kernel would have said, if we had been 1910 | // able to send this message; it's not cached. 1911 | return fuse.ErrNotCached 1912 | } 1913 | // Delay logging until after we can record the error too. We 1914 | // consider a /dev/fuse write to be instantaneous enough to not 1915 | // need separate before and after messages. 1916 | err := s.conn.NotifyStore(id, offset, data) 1917 | s.debug(notification{ 1918 | Op: "NotifyStore", 1919 | Node: id, 1920 | Out: notifyStoreRetrieveDetail{ 1921 | Off: offset, 1922 | Size: uint64(len(data)), 1923 | }, 1924 | Err: errstr(err), 1925 | }) 1926 | return err 1927 | } 1928 | 1929 | // NotifyRetrieve gets data from the kernel page cache. 1930 | // 1931 | // Returns fuse.ErrNotCached if the kernel is not currently caching 1932 | // the node. 1933 | func (s *Server) NotifyRetrieve(node Node, offset uint64, size uint32) ([]byte, error) { 1934 | s.meta.Lock() 1935 | id, ok := s.getNodeId(node) 1936 | if ok { 1937 | snode := s.node[id] 1938 | snode.wg.Add(1) 1939 | defer snode.wg.Done() 1940 | } 1941 | s.meta.Unlock() 1942 | if !ok { 1943 | // This is what the kernel would have said, if we had been 1944 | // able to send this message; it's not cached. 1945 | return nil, fuse.ErrNotCached 1946 | } 1947 | 1948 | ch := make(chan *fuse.NotifyReply, 1) 1949 | s.notifyMu.Lock() 1950 | const wraparoundThreshold = 1 << 63 1951 | if s.notifySeq > wraparoundThreshold { 1952 | s.notifyMu.Unlock() 1953 | return nil, errors.New("running out of notify sequence numbers") 1954 | } 1955 | s.notifySeq++ 1956 | seq := s.notifySeq 1957 | s.notifyWait[seq] = ch 1958 | s.notifyMu.Unlock() 1959 | 1960 | s.debug(notificationRequest{ 1961 | ID: seq, 1962 | Op: "NotifyRetrieve", 1963 | Node: id, 1964 | Out: notifyStoreRetrieveDetail{ 1965 | Off: offset, 1966 | Size: uint64(size), 1967 | }, 1968 | }) 1969 | retrieval, err := s.conn.NotifyRetrieve(seq, id, offset, size) 1970 | if err != nil { 1971 | s.debug(notificationResponse{ 1972 | ID: seq, 1973 | Op: "NotifyRetrieve", 1974 | Err: errstr(err), 1975 | }) 1976 | return nil, err 1977 | } 1978 | 1979 | reply := <-ch 1980 | data := retrieval.Finish(reply) 1981 | s.debug(notificationResponse{ 1982 | ID: seq, 1983 | Op: "NotifyRetrieve", 1984 | In: notifyRetrieveReplyDetail{ 1985 | Size: uint64(len(data)), 1986 | }, 1987 | }) 1988 | return data, nil 1989 | } 1990 | 1991 | func (s *Server) NotifyPollWakeup(wakeup fuse.PollWakeup) error { 1992 | // Delay logging until after we can record the error too. We 1993 | // consider a /dev/fuse write to be instantaneous enough to not 1994 | // need separate before and after messages. 1995 | err := s.conn.NotifyPollWakeup(wakeup) 1996 | s.debug(notification{ 1997 | Op: "NotifyPollWakeup", 1998 | Out: wakeup, 1999 | Err: errstr(err), 2000 | }) 2001 | return err 2002 | } 2003 | 2004 | // DataHandle returns a read-only Handle that satisfies reads 2005 | // using the given data. 2006 | func DataHandle(data []byte) Handle { 2007 | return &dataHandle{data} 2008 | } 2009 | 2010 | type dataHandle struct { 2011 | data []byte 2012 | } 2013 | 2014 | func (d *dataHandle) ReadAll(ctx context.Context) ([]byte, error) { 2015 | return d.data, nil 2016 | } 2017 | 2018 | // GenerateDynamicInode returns a dynamic inode. 2019 | // 2020 | // The parent inode and current entry name are used as the criteria 2021 | // for choosing a pseudorandom inode. This makes it likely the same 2022 | // entry will get the same inode on multiple runs. 2023 | func GenerateDynamicInode(parent uint64, name string) uint64 { 2024 | h := fnv.New64a() 2025 | var buf [8]byte 2026 | binary.LittleEndian.PutUint64(buf[:], parent) 2027 | _, _ = h.Write(buf[:]) 2028 | _, _ = h.Write([]byte(name)) 2029 | var inode uint64 2030 | for { 2031 | inode = h.Sum64() 2032 | if inode > 1 { 2033 | break 2034 | } 2035 | // there's a tiny probability that result is zero or the 2036 | // hardcoded root inode 1; change the input a little and try 2037 | // again 2038 | _, _ = h.Write([]byte{'x'}) 2039 | } 2040 | return inode 2041 | } 2042 | -------------------------------------------------------------------------------- /fs/serve_darwin_test.go: -------------------------------------------------------------------------------- 1 | package fs_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/seaweedfs/fuse/fs/fstestutil" 7 | "golang.org/x/sys/unix" 8 | ) 9 | 10 | type exchangeData struct { 11 | fstestutil.File 12 | // this struct cannot be zero size or multiple instances may look identical 13 | _ int 14 | } 15 | 16 | func TestExchangeDataNotSupported(t *testing.T) { 17 | t.Parallel() 18 | mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{ 19 | "one": &exchangeData{}, 20 | "two": &exchangeData{}, 21 | }}, nil) 22 | if err != nil { 23 | t.Fatal(err) 24 | } 25 | defer mnt.Close() 26 | 27 | if err := unix.Exchangedata(mnt.Dir+"/one", mnt.Dir+"/two", 0); err != unix.ENOTSUP { 28 | t.Fatalf("expected ENOTSUP from exchangedata: %v", err) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /fs/tree.go: -------------------------------------------------------------------------------- 1 | // FUSE directory tree, for servers that wish to use it with the service loop. 2 | 3 | package fs 4 | 5 | import ( 6 | "context" 7 | "os" 8 | pathpkg "path" 9 | "strings" 10 | ) 11 | 12 | import ( 13 | "github.com/seaweedfs/fuse" 14 | ) 15 | 16 | // A Tree implements a basic read-only directory tree for FUSE. 17 | // The Nodes contained in it may still be writable. 18 | type Tree struct { 19 | tree 20 | } 21 | 22 | func (t *Tree) Root() (Node, error) { 23 | return &t.tree, nil 24 | } 25 | 26 | // Add adds the path to the tree, resolving to the given node. 27 | // If path or a prefix of path has already been added to the tree, 28 | // Add panics. 29 | // 30 | // Add is only safe to call before starting to serve requests. 31 | func (t *Tree) Add(path string, node Node) { 32 | path = pathpkg.Clean("/" + path)[1:] 33 | elems := strings.Split(path, "/") 34 | dir := Node(&t.tree) 35 | for i, elem := range elems { 36 | dt, ok := dir.(*tree) 37 | if !ok { 38 | panic("fuse: Tree.Add for " + strings.Join(elems[:i], "/") + " and " + path) 39 | } 40 | n := dt.lookup(elem) 41 | if n != nil { 42 | if i+1 == len(elems) { 43 | panic("fuse: Tree.Add for " + path + " conflicts with " + elem) 44 | } 45 | dir = n 46 | } else { 47 | if i+1 == len(elems) { 48 | dt.add(elem, node) 49 | } else { 50 | dir = &tree{} 51 | dt.add(elem, dir) 52 | } 53 | } 54 | } 55 | } 56 | 57 | type treeDir struct { 58 | name string 59 | node Node 60 | } 61 | 62 | type tree struct { 63 | dir []treeDir 64 | } 65 | 66 | func (t *tree) lookup(name string) Node { 67 | for _, d := range t.dir { 68 | if d.name == name { 69 | return d.node 70 | } 71 | } 72 | return nil 73 | } 74 | 75 | func (t *tree) add(name string, n Node) { 76 | t.dir = append(t.dir, treeDir{name, n}) 77 | } 78 | 79 | func (t *tree) Attr(ctx context.Context, a *fuse.Attr) error { 80 | a.Mode = os.ModeDir | 0555 81 | return nil 82 | } 83 | 84 | func (t *tree) Lookup(ctx context.Context, name string) (Node, error) { 85 | n := t.lookup(name) 86 | if n != nil { 87 | return n, nil 88 | } 89 | return nil, fuse.ENOENT 90 | } 91 | 92 | func (t *tree) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { 93 | var out []fuse.Dirent 94 | for _, d := range t.dir { 95 | out = append(out, fuse.Dirent{Name: d.name}) 96 | } 97 | return out, nil 98 | } 99 | -------------------------------------------------------------------------------- /fuse_darwin.go: -------------------------------------------------------------------------------- 1 | package fuse 2 | 3 | // Maximum file write size we are prepared to receive from the kernel. 4 | // 5 | // This value has to be >=16MB or OSXFUSE (3.4.0 observed) will 6 | // forcibly close the /dev/fuse file descriptor on a Setxattr with a 7 | // 16MB value. See TestSetxattr16MB and 8 | // https://github.com/bazil/fuse/issues/42 9 | const maxWrite = 16 * 1024 * 1024 10 | -------------------------------------------------------------------------------- /fuse_freebsd.go: -------------------------------------------------------------------------------- 1 | package fuse 2 | 3 | // Maximum file write size we are prepared to receive from the kernel. 4 | // 5 | // This number is just a guess. 6 | const maxWrite = 128 * 1024 7 | -------------------------------------------------------------------------------- /fuse_kernel.go: -------------------------------------------------------------------------------- 1 | // See the file LICENSE for copyright and licensing information. 2 | 3 | // Derived from FUSE's fuse_kernel.h, which carries this notice: 4 | /* 5 | This file defines the kernel interface of FUSE 6 | Copyright (C) 2001-2007 Miklos Szeredi 7 | 8 | 9 | This -- and only this -- header file may also be distributed under 10 | the terms of the BSD Licence as follows: 11 | 12 | Copyright (C) 2001-2007 Miklos Szeredi. All rights reserved. 13 | 14 | Redistribution and use in source and binary forms, with or without 15 | modification, are permitted provided that the following conditions 16 | are met: 17 | 1. Redistributions of source code must retain the above copyright 18 | notice, this list of conditions and the following disclaimer. 19 | 2. Redistributions in binary form must reproduce the above copyright 20 | notice, this list of conditions and the following disclaimer in the 21 | documentation and/or other materials provided with the distribution. 22 | 23 | THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND 24 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 26 | ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE 27 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 29 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 30 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 32 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 33 | SUCH DAMAGE. 34 | */ 35 | 36 | package fuse 37 | 38 | import ( 39 | "fmt" 40 | "syscall" 41 | "unsafe" 42 | 43 | "golang.org/x/sys/unix" 44 | ) 45 | 46 | // The FUSE version implemented by the package. 47 | const ( 48 | protoVersionMinMajor = 7 49 | protoVersionMinMinor = 17 50 | protoVersionMaxMajor = 7 51 | protoVersionMaxMinor = 17 52 | ) 53 | 54 | const ( 55 | rootID = 1 56 | ) 57 | 58 | type kstatfs struct { 59 | Blocks uint64 60 | Bfree uint64 61 | Bavail uint64 62 | Files uint64 63 | Ffree uint64 64 | Bsize uint32 65 | Namelen uint32 66 | Frsize uint32 67 | _ uint32 68 | Spare [6]uint32 69 | } 70 | 71 | // GetattrFlags are bit flags that can be seen in GetattrRequest. 72 | type GetattrFlags uint32 73 | 74 | const ( 75 | // Indicates the handle is valid. 76 | GetattrFh GetattrFlags = 1 << 0 77 | ) 78 | 79 | var getattrFlagsNames = []flagName{ 80 | {uint32(GetattrFh), "GetattrFh"}, 81 | } 82 | 83 | func (fl GetattrFlags) String() string { 84 | return flagString(uint32(fl), getattrFlagsNames) 85 | } 86 | 87 | // The SetattrValid are bit flags describing which fields in the SetattrRequest 88 | // are included in the change. 89 | type SetattrValid uint32 90 | 91 | const ( 92 | SetattrMode SetattrValid = 1 << 0 93 | SetattrUid SetattrValid = 1 << 1 94 | SetattrGid SetattrValid = 1 << 2 95 | SetattrSize SetattrValid = 1 << 3 96 | SetattrAtime SetattrValid = 1 << 4 97 | SetattrMtime SetattrValid = 1 << 5 98 | SetattrHandle SetattrValid = 1 << 6 99 | 100 | // Linux only(?) 101 | SetattrAtimeNow SetattrValid = 1 << 7 102 | SetattrMtimeNow SetattrValid = 1 << 8 103 | SetattrLockOwner SetattrValid = 1 << 9 // http://www.mail-archive.com/git-commits-head@vger.kernel.org/msg27852.html 104 | 105 | // OS X only 106 | SetattrCrtime SetattrValid = 1 << 28 107 | SetattrChgtime SetattrValid = 1 << 29 108 | SetattrBkuptime SetattrValid = 1 << 30 109 | SetattrFlags SetattrValid = 1 << 31 110 | ) 111 | 112 | func (fl SetattrValid) Mode() bool { return fl&SetattrMode != 0 } 113 | func (fl SetattrValid) Uid() bool { return fl&SetattrUid != 0 } 114 | func (fl SetattrValid) Gid() bool { return fl&SetattrGid != 0 } 115 | func (fl SetattrValid) Size() bool { return fl&SetattrSize != 0 } 116 | func (fl SetattrValid) Atime() bool { return fl&SetattrAtime != 0 } 117 | func (fl SetattrValid) Mtime() bool { return fl&SetattrMtime != 0 } 118 | func (fl SetattrValid) Handle() bool { return fl&SetattrHandle != 0 } 119 | func (fl SetattrValid) AtimeNow() bool { return fl&SetattrAtimeNow != 0 } 120 | func (fl SetattrValid) MtimeNow() bool { return fl&SetattrMtimeNow != 0 } 121 | func (fl SetattrValid) LockOwner() bool { return fl&SetattrLockOwner != 0 } 122 | func (fl SetattrValid) Crtime() bool { return fl&SetattrCrtime != 0 } 123 | func (fl SetattrValid) Chgtime() bool { return fl&SetattrChgtime != 0 } 124 | func (fl SetattrValid) Bkuptime() bool { return fl&SetattrBkuptime != 0 } 125 | func (fl SetattrValid) Flags() bool { return fl&SetattrFlags != 0 } 126 | 127 | func (fl SetattrValid) String() string { 128 | return flagString(uint32(fl), setattrValidNames) 129 | } 130 | 131 | var setattrValidNames = []flagName{ 132 | {uint32(SetattrMode), "SetattrMode"}, 133 | {uint32(SetattrUid), "SetattrUid"}, 134 | {uint32(SetattrGid), "SetattrGid"}, 135 | {uint32(SetattrSize), "SetattrSize"}, 136 | {uint32(SetattrAtime), "SetattrAtime"}, 137 | {uint32(SetattrMtime), "SetattrMtime"}, 138 | {uint32(SetattrHandle), "SetattrHandle"}, 139 | {uint32(SetattrAtimeNow), "SetattrAtimeNow"}, 140 | {uint32(SetattrMtimeNow), "SetattrMtimeNow"}, 141 | {uint32(SetattrLockOwner), "SetattrLockOwner"}, 142 | {uint32(SetattrCrtime), "SetattrCrtime"}, 143 | {uint32(SetattrChgtime), "SetattrChgtime"}, 144 | {uint32(SetattrBkuptime), "SetattrBkuptime"}, 145 | {uint32(SetattrFlags), "SetattrFlags"}, 146 | } 147 | 148 | // Flags that can be seen in OpenRequest.Flags. 149 | const ( 150 | // Access modes. These are not 1-bit flags, but alternatives where 151 | // only one can be chosen. See the IsReadOnly etc convenience 152 | // methods. 153 | OpenReadOnly OpenFlags = syscall.O_RDONLY 154 | OpenWriteOnly OpenFlags = syscall.O_WRONLY 155 | OpenReadWrite OpenFlags = syscall.O_RDWR 156 | 157 | // File was opened in append-only mode, all writes will go to end 158 | // of file. OS X does not provide this information. 159 | OpenAppend OpenFlags = syscall.O_APPEND 160 | OpenCreate OpenFlags = syscall.O_CREAT 161 | OpenDirectory OpenFlags = syscall.O_DIRECTORY 162 | OpenExclusive OpenFlags = syscall.O_EXCL 163 | OpenNonblock OpenFlags = syscall.O_NONBLOCK 164 | OpenSync OpenFlags = syscall.O_SYNC 165 | OpenTruncate OpenFlags = syscall.O_TRUNC 166 | ) 167 | 168 | // OpenAccessModeMask is a bitmask that separates the access mode 169 | // from the other flags in OpenFlags. 170 | const OpenAccessModeMask OpenFlags = syscall.O_ACCMODE 171 | 172 | // OpenFlags are the O_FOO flags passed to open/create/etc calls. For 173 | // example, os.O_WRONLY | os.O_APPEND. 174 | type OpenFlags uint32 175 | 176 | func (fl OpenFlags) String() string { 177 | // O_RDONLY, O_RWONLY, O_RDWR are not flags 178 | s := accModeName(fl & OpenAccessModeMask) 179 | flags := uint32(fl &^ OpenAccessModeMask) 180 | if flags != 0 { 181 | s = s + "+" + flagString(flags, openFlagNames) 182 | } 183 | return s 184 | } 185 | 186 | // Return true if OpenReadOnly is set. 187 | func (fl OpenFlags) IsReadOnly() bool { 188 | return fl&OpenAccessModeMask == OpenReadOnly 189 | } 190 | 191 | // Return true if OpenWriteOnly is set. 192 | func (fl OpenFlags) IsWriteOnly() bool { 193 | return fl&OpenAccessModeMask == OpenWriteOnly 194 | } 195 | 196 | // Return true if OpenReadWrite is set. 197 | func (fl OpenFlags) IsReadWrite() bool { 198 | return fl&OpenAccessModeMask == OpenReadWrite 199 | } 200 | 201 | func accModeName(flags OpenFlags) string { 202 | switch flags { 203 | case OpenReadOnly: 204 | return "OpenReadOnly" 205 | case OpenWriteOnly: 206 | return "OpenWriteOnly" 207 | case OpenReadWrite: 208 | return "OpenReadWrite" 209 | default: 210 | return "" 211 | } 212 | } 213 | 214 | var openFlagNames = []flagName{ 215 | {uint32(OpenAppend), "OpenAppend"}, 216 | {uint32(OpenCreate), "OpenCreate"}, 217 | {uint32(OpenDirectory), "OpenDirectory"}, 218 | {uint32(OpenExclusive), "OpenExclusive"}, 219 | {uint32(OpenNonblock), "OpenNonblock"}, 220 | {uint32(OpenSync), "OpenSync"}, 221 | {uint32(OpenTruncate), "OpenTruncate"}, 222 | } 223 | 224 | // The OpenResponseFlags are returned in the OpenResponse. 225 | type OpenResponseFlags uint32 226 | 227 | const ( 228 | OpenDirectIO OpenResponseFlags = 1 << 0 // bypass page cache for this open file 229 | OpenKeepCache OpenResponseFlags = 1 << 1 // don't invalidate the data cache on open 230 | OpenNonSeekable OpenResponseFlags = 1 << 2 // mark the file as non-seekable (not supported on OS X) 231 | 232 | OpenPurgeAttr OpenResponseFlags = 1 << 30 // OS X 233 | OpenPurgeUBC OpenResponseFlags = 1 << 31 // OS X 234 | ) 235 | 236 | func (fl OpenResponseFlags) String() string { 237 | return flagString(uint32(fl), openResponseFlagNames) 238 | } 239 | 240 | var openResponseFlagNames = []flagName{ 241 | {uint32(OpenDirectIO), "OpenDirectIO"}, 242 | {uint32(OpenKeepCache), "OpenKeepCache"}, 243 | {uint32(OpenNonSeekable), "OpenNonSeekable"}, 244 | {uint32(OpenPurgeAttr), "OpenPurgeAttr"}, 245 | {uint32(OpenPurgeUBC), "OpenPurgeUBC"}, 246 | } 247 | 248 | // The InitFlags are used in the Init exchange. 249 | type InitFlags uint32 250 | 251 | const ( 252 | InitAsyncRead InitFlags = 1 << 0 253 | InitPOSIXLocks InitFlags = 1 << 1 254 | InitFileOps InitFlags = 1 << 2 255 | InitAtomicTrunc InitFlags = 1 << 3 256 | InitExportSupport InitFlags = 1 << 4 257 | InitBigWrites InitFlags = 1 << 5 258 | // Do not mask file access modes with umask. Not supported on OS X. 259 | InitDontMask InitFlags = 1 << 6 260 | InitSpliceWrite InitFlags = 1 << 7 261 | InitSpliceMove InitFlags = 1 << 8 262 | InitSpliceRead InitFlags = 1 << 9 263 | InitFlockLocks InitFlags = 1 << 10 264 | InitHasIoctlDir InitFlags = 1 << 11 265 | InitAutoInvalData InitFlags = 1 << 12 266 | InitDoReaddirplus InitFlags = 1 << 13 267 | InitReaddirplusAuto InitFlags = 1 << 14 268 | InitAsyncDIO InitFlags = 1 << 15 269 | InitWritebackCache InitFlags = 1 << 16 270 | InitNoOpenSupport InitFlags = 1 << 17 271 | 272 | InitCaseSensitive InitFlags = 1 << 29 // OS X only 273 | InitVolRename InitFlags = 1 << 30 // OS X only 274 | InitXtimes InitFlags = 1 << 31 // OS X only 275 | ) 276 | 277 | type flagName struct { 278 | bit uint32 279 | name string 280 | } 281 | 282 | var initFlagNames = []flagName{ 283 | {uint32(InitAsyncRead), "InitAsyncRead"}, 284 | {uint32(InitPOSIXLocks), "InitPOSIXLocks"}, 285 | {uint32(InitFileOps), "InitFileOps"}, 286 | {uint32(InitAtomicTrunc), "InitAtomicTrunc"}, 287 | {uint32(InitExportSupport), "InitExportSupport"}, 288 | {uint32(InitBigWrites), "InitBigWrites"}, 289 | {uint32(InitDontMask), "InitDontMask"}, 290 | {uint32(InitSpliceWrite), "InitSpliceWrite"}, 291 | {uint32(InitSpliceMove), "InitSpliceMove"}, 292 | {uint32(InitSpliceRead), "InitSpliceRead"}, 293 | {uint32(InitFlockLocks), "InitFlockLocks"}, 294 | {uint32(InitHasIoctlDir), "InitHasIoctlDir"}, 295 | {uint32(InitAutoInvalData), "InitAutoInvalData"}, 296 | {uint32(InitDoReaddirplus), "InitDoReaddirplus"}, 297 | {uint32(InitReaddirplusAuto), "InitReaddirplusAuto"}, 298 | {uint32(InitAsyncDIO), "InitAsyncDIO"}, 299 | {uint32(InitWritebackCache), "InitWritebackCache"}, 300 | {uint32(InitNoOpenSupport), "InitNoOpenSupport"}, 301 | 302 | {uint32(InitCaseSensitive), "InitCaseSensitive"}, 303 | {uint32(InitVolRename), "InitVolRename"}, 304 | {uint32(InitXtimes), "InitXtimes"}, 305 | } 306 | 307 | func (fl InitFlags) String() string { 308 | return flagString(uint32(fl), initFlagNames) 309 | } 310 | 311 | func flagString(f uint32, names []flagName) string { 312 | var s string 313 | 314 | if f == 0 { 315 | return "0" 316 | } 317 | 318 | for _, n := range names { 319 | if f&n.bit != 0 { 320 | s += "+" + n.name 321 | f &^= n.bit 322 | } 323 | } 324 | if f != 0 { 325 | s += fmt.Sprintf("%+#x", f) 326 | } 327 | return s[1:] 328 | } 329 | 330 | // The ReleaseFlags are used in the Release exchange. 331 | type ReleaseFlags uint32 332 | 333 | const ( 334 | ReleaseFlush ReleaseFlags = 1 << 0 335 | ReleaseFlockUnlock ReleaseFlags = 1 << 1 336 | ) 337 | 338 | func (fl ReleaseFlags) String() string { 339 | return flagString(uint32(fl), releaseFlagNames) 340 | } 341 | 342 | var releaseFlagNames = []flagName{ 343 | {uint32(ReleaseFlush), "ReleaseFlush"}, 344 | {uint32(ReleaseFlockUnlock), "ReleaseFlockUnlock"}, 345 | } 346 | 347 | // Opcodes 348 | const ( 349 | opLookup = 1 350 | opForget = 2 // no reply 351 | opGetattr = 3 352 | opSetattr = 4 353 | opReadlink = 5 354 | opSymlink = 6 355 | opMknod = 8 356 | opMkdir = 9 357 | opUnlink = 10 358 | opRmdir = 11 359 | opRename = 12 360 | opLink = 13 361 | opOpen = 14 362 | opRead = 15 363 | opWrite = 16 364 | opStatfs = 17 365 | opRelease = 18 366 | opFsync = 20 367 | opSetxattr = 21 368 | opGetxattr = 22 369 | opListxattr = 23 370 | opRemovexattr = 24 371 | opFlush = 25 372 | opInit = 26 373 | opOpendir = 27 374 | opReaddir = 28 375 | opReleasedir = 29 376 | opFsyncdir = 30 377 | opGetlk = 31 378 | opSetlk = 32 379 | opSetlkw = 33 380 | opAccess = 34 381 | opCreate = 35 382 | opInterrupt = 36 383 | opBmap = 37 384 | opDestroy = 38 385 | opIoctl = 39 386 | opPoll = 40 387 | opNotifyReply = 41 388 | opBatchForget = 42 389 | 390 | // OS X 391 | opSetvolname = 61 392 | opGetxtimes = 62 393 | opExchange = 63 394 | ) 395 | 396 | type entryOut struct { 397 | Nodeid uint64 // Inode ID 398 | Generation uint64 // Inode generation 399 | EntryValid uint64 // Cache timeout for the name 400 | AttrValid uint64 // Cache timeout for the attributes 401 | EntryValidNsec uint32 402 | AttrValidNsec uint32 403 | Attr attr 404 | } 405 | 406 | func entryOutSize(p Protocol) uintptr { 407 | switch { 408 | case p.LT(Protocol{7, 9}): 409 | return unsafe.Offsetof(entryOut{}.Attr) + unsafe.Offsetof(entryOut{}.Attr.Blksize) 410 | default: 411 | return unsafe.Sizeof(entryOut{}) 412 | } 413 | } 414 | 415 | type forgetIn struct { 416 | Nlookup uint64 417 | } 418 | 419 | type forgetOne struct { 420 | NodeID uint64 421 | Nlookup uint64 422 | } 423 | 424 | type batchForgetIn struct { 425 | Count uint32 426 | _ uint32 427 | } 428 | 429 | type getattrIn struct { 430 | GetattrFlags uint32 431 | _ uint32 432 | Fh uint64 433 | } 434 | 435 | type attrOut struct { 436 | AttrValid uint64 // Cache timeout for the attributes 437 | AttrValidNsec uint32 438 | _ uint32 439 | Attr attr 440 | } 441 | 442 | func attrOutSize(p Protocol) uintptr { 443 | switch { 444 | case p.LT(Protocol{7, 9}): 445 | return unsafe.Offsetof(attrOut{}.Attr) + unsafe.Offsetof(attrOut{}.Attr.Blksize) 446 | default: 447 | return unsafe.Sizeof(attrOut{}) 448 | } 449 | } 450 | 451 | // OS X 452 | type getxtimesOut struct { 453 | Bkuptime uint64 454 | Crtime uint64 455 | BkuptimeNsec uint32 456 | CrtimeNsec uint32 457 | } 458 | 459 | type mknodIn struct { 460 | Mode uint32 461 | Rdev uint32 462 | Umask uint32 463 | _ uint32 464 | // "filename\x00" follows. 465 | } 466 | 467 | func mknodInSize(p Protocol) uintptr { 468 | switch { 469 | case p.LT(Protocol{7, 12}): 470 | return unsafe.Offsetof(mknodIn{}.Umask) 471 | default: 472 | return unsafe.Sizeof(mknodIn{}) 473 | } 474 | } 475 | 476 | type mkdirIn struct { 477 | Mode uint32 478 | Umask uint32 479 | // filename follows 480 | } 481 | 482 | func mkdirInSize(p Protocol) uintptr { 483 | switch { 484 | case p.LT(Protocol{7, 12}): 485 | return unsafe.Offsetof(mkdirIn{}.Umask) + 4 486 | default: 487 | return unsafe.Sizeof(mkdirIn{}) 488 | } 489 | } 490 | 491 | type renameIn struct { 492 | Newdir uint64 493 | // "oldname\x00newname\x00" follows 494 | } 495 | 496 | // OS X 497 | type exchangeIn struct { 498 | Olddir uint64 499 | Newdir uint64 500 | Options uint64 501 | // "oldname\x00newname\x00" follows 502 | } 503 | 504 | type linkIn struct { 505 | Oldnodeid uint64 506 | } 507 | 508 | type setattrInCommon struct { 509 | Valid uint32 510 | _ uint32 511 | Fh uint64 512 | Size uint64 513 | LockOwner uint64 // unused on OS X? 514 | Atime uint64 515 | Mtime uint64 516 | Unused2 uint64 517 | AtimeNsec uint32 518 | MtimeNsec uint32 519 | Unused3 uint32 520 | Mode uint32 521 | Unused4 uint32 522 | Uid uint32 523 | Gid uint32 524 | Unused5 uint32 525 | } 526 | 527 | type openIn struct { 528 | Flags uint32 529 | Unused uint32 530 | } 531 | 532 | type openOut struct { 533 | Fh uint64 534 | OpenFlags uint32 535 | _ uint32 536 | } 537 | 538 | type createIn struct { 539 | Flags uint32 540 | Mode uint32 541 | Umask uint32 542 | _ uint32 543 | } 544 | 545 | func createInSize(p Protocol) uintptr { 546 | switch { 547 | case p.LT(Protocol{7, 12}): 548 | return unsafe.Offsetof(createIn{}.Umask) 549 | default: 550 | return unsafe.Sizeof(createIn{}) 551 | } 552 | } 553 | 554 | type releaseIn struct { 555 | Fh uint64 556 | Flags uint32 557 | ReleaseFlags uint32 558 | LockOwner uint64 559 | } 560 | 561 | type flushIn struct { 562 | Fh uint64 563 | FlushFlags uint32 564 | _ uint32 565 | LockOwner uint64 566 | } 567 | 568 | type readIn struct { 569 | Fh uint64 570 | Offset uint64 571 | Size uint32 572 | ReadFlags uint32 573 | LockOwner uint64 574 | Flags uint32 575 | _ uint32 576 | } 577 | 578 | func readInSize(p Protocol) uintptr { 579 | switch { 580 | case p.LT(Protocol{7, 9}): 581 | return unsafe.Offsetof(readIn{}.ReadFlags) + 4 582 | default: 583 | return unsafe.Sizeof(readIn{}) 584 | } 585 | } 586 | 587 | // The ReadFlags are passed in ReadRequest. 588 | type ReadFlags uint32 589 | 590 | const ( 591 | // LockOwner field is valid. 592 | ReadLockOwner ReadFlags = 1 << 1 593 | ) 594 | 595 | var readFlagNames = []flagName{ 596 | {uint32(ReadLockOwner), "ReadLockOwner"}, 597 | } 598 | 599 | func (fl ReadFlags) String() string { 600 | return flagString(uint32(fl), readFlagNames) 601 | } 602 | 603 | type writeIn struct { 604 | Fh uint64 605 | Offset uint64 606 | Size uint32 607 | WriteFlags uint32 608 | LockOwner uint64 609 | Flags uint32 610 | _ uint32 611 | } 612 | 613 | func writeInSize(p Protocol) uintptr { 614 | switch { 615 | case p.LT(Protocol{7, 9}): 616 | return unsafe.Offsetof(writeIn{}.LockOwner) 617 | default: 618 | return unsafe.Sizeof(writeIn{}) 619 | } 620 | } 621 | 622 | type writeOut struct { 623 | Size uint32 624 | _ uint32 625 | } 626 | 627 | // The WriteFlags are passed in WriteRequest. 628 | type WriteFlags uint32 629 | 630 | const ( 631 | WriteCache WriteFlags = 1 << 0 632 | // LockOwner field is valid. 633 | WriteLockOwner WriteFlags = 1 << 1 634 | ) 635 | 636 | var writeFlagNames = []flagName{ 637 | {uint32(WriteCache), "WriteCache"}, 638 | {uint32(WriteLockOwner), "WriteLockOwner"}, 639 | } 640 | 641 | func (fl WriteFlags) String() string { 642 | return flagString(uint32(fl), writeFlagNames) 643 | } 644 | 645 | const compatStatfsSize = 48 646 | 647 | type statfsOut struct { 648 | St kstatfs 649 | } 650 | 651 | type fsyncIn struct { 652 | Fh uint64 653 | FsyncFlags uint32 654 | _ uint32 655 | } 656 | 657 | type setxattrInCommon struct { 658 | Size uint32 659 | Flags uint32 660 | } 661 | 662 | func (setxattrInCommon) position() uint32 { 663 | return 0 664 | } 665 | 666 | type getxattrInCommon struct { 667 | Size uint32 668 | _ uint32 669 | } 670 | 671 | func (getxattrInCommon) position() uint32 { 672 | return 0 673 | } 674 | 675 | type getxattrOut struct { 676 | Size uint32 677 | _ uint32 678 | } 679 | 680 | // The LockFlags are passed in LockRequest or LockWaitRequest. 681 | type LockFlags uint32 682 | 683 | const ( 684 | // BSD-style flock lock (not POSIX lock) 685 | LockFlock LockFlags = 1 << 0 686 | ) 687 | 688 | var lockFlagNames = []flagName{ 689 | {uint32(LockFlock), "LockFlock"}, 690 | } 691 | 692 | func (fl LockFlags) String() string { 693 | return flagString(uint32(fl), lockFlagNames) 694 | } 695 | 696 | type LockType uint32 697 | 698 | const ( 699 | // It seems FreeBSD FUSE passes these through using its local 700 | // values, not whatever Linux enshrined into the protocol. It's 701 | // unclear what the intended behavior is. 702 | 703 | LockRead LockType = unix.F_RDLCK 704 | LockWrite LockType = unix.F_WRLCK 705 | LockUnlock LockType = unix.F_UNLCK 706 | ) 707 | 708 | var lockTypeNames = map[LockType]string{ 709 | LockRead: "LockRead", 710 | LockWrite: "LockWrite", 711 | LockUnlock: "LockUnlock", 712 | } 713 | 714 | func (l LockType) String() string { 715 | s, ok := lockTypeNames[l] 716 | if ok { 717 | return s 718 | } 719 | return fmt.Sprintf("LockType(%d)", l) 720 | } 721 | 722 | type fileLock struct { 723 | Start uint64 724 | End uint64 725 | Type uint32 726 | PID uint32 727 | } 728 | 729 | type lkIn struct { 730 | Fh uint64 731 | Owner uint64 732 | Lk fileLock 733 | LkFlags uint32 734 | _ uint32 735 | } 736 | 737 | type lkOut struct { 738 | Lk fileLock 739 | } 740 | 741 | type accessIn struct { 742 | Mask uint32 743 | _ uint32 744 | } 745 | 746 | type initIn struct { 747 | Major uint32 748 | Minor uint32 749 | MaxReadahead uint32 750 | Flags uint32 751 | } 752 | 753 | const initInSize = int(unsafe.Sizeof(initIn{})) 754 | 755 | type initOut struct { 756 | Major uint32 757 | Minor uint32 758 | MaxReadahead uint32 759 | Flags uint32 760 | MaxBackground uint16 761 | CongestionThreshold uint16 762 | MaxWrite uint32 763 | } 764 | 765 | type interruptIn struct { 766 | Unique uint64 767 | } 768 | 769 | type bmapIn struct { 770 | Block uint64 771 | BlockSize uint32 772 | _ uint32 773 | } 774 | 775 | type bmapOut struct { 776 | Block uint64 777 | } 778 | 779 | type inHeader struct { 780 | Len uint32 781 | Opcode uint32 782 | Unique uint64 783 | Nodeid uint64 784 | Uid uint32 785 | Gid uint32 786 | Pid uint32 787 | _ uint32 788 | } 789 | 790 | const inHeaderSize = int(unsafe.Sizeof(inHeader{})) 791 | 792 | type outHeader struct { 793 | Len uint32 794 | Error int32 795 | Unique uint64 796 | } 797 | 798 | type dirent struct { 799 | Ino uint64 800 | Off uint64 801 | Namelen uint32 802 | Type uint32 803 | Name [0]byte 804 | } 805 | 806 | const direntSize = 8 + 8 + 4 + 4 807 | 808 | const ( 809 | notifyCodePoll int32 = 1 810 | notifyCodeInvalInode int32 = 2 811 | notifyCodeInvalEntry int32 = 3 812 | notifyCodeStore int32 = 4 813 | notifyCodeRetrieve int32 = 5 814 | ) 815 | 816 | type notifyInvalInodeOut struct { 817 | Ino uint64 818 | Off int64 819 | Len int64 820 | } 821 | 822 | type notifyInvalEntryOut struct { 823 | Parent uint64 824 | Namelen uint32 825 | _ uint32 826 | } 827 | 828 | type notifyStoreOut struct { 829 | Nodeid uint64 830 | Offset uint64 831 | Size uint32 832 | _ uint32 833 | } 834 | 835 | type notifyRetrieveOut struct { 836 | NotifyUnique uint64 837 | Nodeid uint64 838 | Offset uint64 839 | Size uint32 840 | _ uint32 841 | } 842 | 843 | type notifyRetrieveIn struct { 844 | // matches writeIn 845 | 846 | _ uint64 847 | Offset uint64 848 | Size uint32 849 | _ uint32 850 | _ uint64 851 | _ uint64 852 | } 853 | 854 | // PollFlags are passed in PollRequest.Flags 855 | type PollFlags uint32 856 | 857 | const ( 858 | // PollScheduleNotify requests that a poll notification is done 859 | // once the node is ready. 860 | PollScheduleNotify PollFlags = 1 << 0 861 | ) 862 | 863 | var pollFlagNames = []flagName{ 864 | {uint32(PollScheduleNotify), "PollScheduleNotify"}, 865 | } 866 | 867 | func (fl PollFlags) String() string { 868 | return flagString(uint32(fl), pollFlagNames) 869 | } 870 | 871 | type PollEvents uint32 872 | 873 | const ( 874 | PollIn PollEvents = 0x0000_0001 875 | PollPriority PollEvents = 0x0000_0002 876 | PollOut PollEvents = 0x0000_0004 877 | PollError PollEvents = 0x0000_0008 878 | PollHangup PollEvents = 0x0000_0010 879 | // PollInvalid doesn't seem to be used in the FUSE protocol. 880 | PollInvalid PollEvents = 0x0000_0020 881 | PollReadNormal PollEvents = 0x0000_0040 882 | PollReadOutOfBand PollEvents = 0x0000_0080 883 | PollWriteNormal PollEvents = 0x0000_0100 884 | PollWriteOutOfBand PollEvents = 0x0000_0200 885 | PollMessage PollEvents = 0x0000_0400 886 | PollReadHangup PollEvents = 0x0000_2000 887 | 888 | DefaultPollMask = PollIn | PollOut | PollReadNormal | PollWriteNormal 889 | ) 890 | 891 | var pollEventNames = []flagName{ 892 | {uint32(PollIn), "PollIn"}, 893 | {uint32(PollPriority), "PollPriority"}, 894 | {uint32(PollOut), "PollOut"}, 895 | {uint32(PollError), "PollError"}, 896 | {uint32(PollHangup), "PollHangup"}, 897 | {uint32(PollInvalid), "PollInvalid"}, 898 | {uint32(PollReadNormal), "PollReadNormal"}, 899 | {uint32(PollReadOutOfBand), "PollReadOutOfBand"}, 900 | {uint32(PollWriteNormal), "PollWriteNormal"}, 901 | {uint32(PollWriteOutOfBand), "PollWriteOutOfBand"}, 902 | {uint32(PollMessage), "PollMessage"}, 903 | {uint32(PollReadHangup), "PollReadHangup"}, 904 | } 905 | 906 | func (fl PollEvents) String() string { 907 | return flagString(uint32(fl), pollEventNames) 908 | } 909 | 910 | type pollIn struct { 911 | Fh uint64 912 | Kh uint64 913 | Flags uint32 914 | Events uint32 915 | } 916 | 917 | type pollOut struct { 918 | REvents uint32 919 | _ uint32 920 | } 921 | 922 | type notifyPollWakeupOut struct { 923 | Kh uint64 924 | } 925 | -------------------------------------------------------------------------------- /fuse_kernel_darwin.go: -------------------------------------------------------------------------------- 1 | package fuse 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type attr struct { 8 | Ino uint64 9 | Size uint64 10 | Blocks uint64 11 | Atime uint64 12 | Mtime uint64 13 | Ctime uint64 14 | Crtime_ uint64 // OS X only 15 | AtimeNsec uint32 16 | MtimeNsec uint32 17 | CtimeNsec uint32 18 | CrtimeNsec uint32 // OS X only 19 | Mode uint32 20 | Nlink uint32 21 | Uid uint32 22 | Gid uint32 23 | Rdev uint32 24 | Flags_ uint32 // OS X only; see chflags(2) 25 | Blksize uint32 26 | padding uint32 27 | } 28 | 29 | func (a *attr) SetCrtime(s uint64, ns uint32) { 30 | a.Crtime_, a.CrtimeNsec = s, ns 31 | } 32 | 33 | func (a *attr) SetFlags(f uint32) { 34 | a.Flags_ = f 35 | } 36 | 37 | type setattrIn struct { 38 | setattrInCommon 39 | 40 | // OS X only 41 | Bkuptime_ uint64 42 | Chgtime_ uint64 43 | Crtime uint64 44 | BkuptimeNsec uint32 45 | ChgtimeNsec uint32 46 | CrtimeNsec uint32 47 | Flags_ uint32 // see chflags(2) 48 | } 49 | 50 | func (in *setattrIn) BkupTime() time.Time { 51 | return time.Unix(int64(in.Bkuptime_), int64(in.BkuptimeNsec)) 52 | } 53 | 54 | func (in *setattrIn) Chgtime() time.Time { 55 | return time.Unix(int64(in.Chgtime_), int64(in.ChgtimeNsec)) 56 | } 57 | 58 | func (in *setattrIn) Flags() uint32 { 59 | return in.Flags_ 60 | } 61 | 62 | func openFlags(flags uint32) OpenFlags { 63 | return OpenFlags(flags) 64 | } 65 | 66 | type getxattrIn struct { 67 | getxattrInCommon 68 | 69 | // OS X only 70 | Position uint32 71 | Padding uint32 72 | } 73 | 74 | func (g *getxattrIn) position() uint32 { 75 | return g.Position 76 | } 77 | 78 | type setxattrIn struct { 79 | setxattrInCommon 80 | 81 | // OS X only 82 | Position uint32 83 | Padding uint32 84 | } 85 | 86 | func (s *setxattrIn) position() uint32 { 87 | return s.Position 88 | } 89 | -------------------------------------------------------------------------------- /fuse_kernel_freebsd.go: -------------------------------------------------------------------------------- 1 | package fuse 2 | 3 | import "time" 4 | 5 | type attr struct { 6 | Ino uint64 7 | Size uint64 8 | Blocks uint64 9 | Atime uint64 10 | Mtime uint64 11 | Ctime uint64 12 | AtimeNsec uint32 13 | MtimeNsec uint32 14 | CtimeNsec uint32 15 | Mode uint32 16 | Nlink uint32 17 | Uid uint32 18 | Gid uint32 19 | Rdev uint32 20 | Blksize uint32 21 | padding uint32 22 | } 23 | 24 | func (a *attr) Crtime() time.Time { 25 | return time.Time{} 26 | } 27 | 28 | func (a *attr) SetCrtime(s uint64, ns uint32) { 29 | // ignored on freebsd 30 | } 31 | 32 | func (a *attr) SetFlags(f uint32) { 33 | // ignored on freebsd 34 | } 35 | 36 | type setattrIn struct { 37 | setattrInCommon 38 | } 39 | 40 | func (in *setattrIn) BkupTime() time.Time { 41 | return time.Time{} 42 | } 43 | 44 | func (in *setattrIn) Chgtime() time.Time { 45 | return time.Time{} 46 | } 47 | 48 | func (in *setattrIn) Flags() uint32 { 49 | return 0 50 | } 51 | 52 | func openFlags(flags uint32) OpenFlags { 53 | return OpenFlags(flags) 54 | } 55 | 56 | type getxattrIn struct { 57 | getxattrInCommon 58 | } 59 | 60 | type setxattrIn struct { 61 | setxattrInCommon 62 | } 63 | -------------------------------------------------------------------------------- /fuse_kernel_linux.go: -------------------------------------------------------------------------------- 1 | package fuse 2 | 3 | import "time" 4 | 5 | type attr struct { 6 | Ino uint64 7 | Size uint64 8 | Blocks uint64 9 | Atime uint64 10 | Mtime uint64 11 | Ctime uint64 12 | AtimeNsec uint32 13 | MtimeNsec uint32 14 | CtimeNsec uint32 15 | Mode uint32 16 | Nlink uint32 17 | Uid uint32 18 | Gid uint32 19 | Rdev uint32 20 | Blksize uint32 21 | padding uint32 22 | } 23 | 24 | func (a *attr) Crtime() time.Time { 25 | return time.Time{} 26 | } 27 | 28 | func (a *attr) SetCrtime(s uint64, ns uint32) { 29 | // Ignored on Linux. 30 | } 31 | 32 | func (a *attr) SetFlags(f uint32) { 33 | // Ignored on Linux. 34 | } 35 | 36 | type setattrIn struct { 37 | setattrInCommon 38 | } 39 | 40 | func (in *setattrIn) BkupTime() time.Time { 41 | return time.Time{} 42 | } 43 | 44 | func (in *setattrIn) Chgtime() time.Time { 45 | return time.Time{} 46 | } 47 | 48 | func (in *setattrIn) Flags() uint32 { 49 | return 0 50 | } 51 | 52 | func openFlags(flags uint32) OpenFlags { 53 | // on amd64, the 32-bit O_LARGEFILE flag is always seen; 54 | // on i386, the flag probably depends on the app 55 | // requesting, but in any case should be utterly 56 | // uninteresting to us here; our kernel protocol messages 57 | // are not directly related to the client app's kernel 58 | // API/ABI 59 | flags &^= 0x8000 60 | 61 | return OpenFlags(flags) 62 | } 63 | 64 | type getxattrIn struct { 65 | getxattrInCommon 66 | } 67 | 68 | type setxattrIn struct { 69 | setxattrInCommon 70 | } 71 | -------------------------------------------------------------------------------- /fuse_kernel_std.go: -------------------------------------------------------------------------------- 1 | package fuse 2 | -------------------------------------------------------------------------------- /fuse_kernel_test.go: -------------------------------------------------------------------------------- 1 | package fuse_test 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/seaweedfs/fuse" 8 | ) 9 | 10 | func TestOpenFlagsAccmodeMaskReadWrite(t *testing.T) { 11 | var f = fuse.OpenFlags(os.O_RDWR | os.O_SYNC) 12 | if g, e := f&fuse.OpenAccessModeMask, fuse.OpenReadWrite; g != e { 13 | t.Fatalf("OpenAccessModeMask behaves wrong: %v: %o != %o", f, g, e) 14 | } 15 | if f.IsReadOnly() { 16 | t.Fatalf("IsReadOnly is wrong: %v", f) 17 | } 18 | if f.IsWriteOnly() { 19 | t.Fatalf("IsWriteOnly is wrong: %v", f) 20 | } 21 | if !f.IsReadWrite() { 22 | t.Fatalf("IsReadWrite is wrong: %v", f) 23 | } 24 | } 25 | 26 | func TestOpenFlagsAccmodeMaskReadOnly(t *testing.T) { 27 | var f = fuse.OpenFlags(os.O_RDONLY | os.O_SYNC) 28 | if g, e := f&fuse.OpenAccessModeMask, fuse.OpenReadOnly; g != e { 29 | t.Fatalf("OpenAccessModeMask behaves wrong: %v: %o != %o", f, g, e) 30 | } 31 | if !f.IsReadOnly() { 32 | t.Fatalf("IsReadOnly is wrong: %v", f) 33 | } 34 | if f.IsWriteOnly() { 35 | t.Fatalf("IsWriteOnly is wrong: %v", f) 36 | } 37 | if f.IsReadWrite() { 38 | t.Fatalf("IsReadWrite is wrong: %v", f) 39 | } 40 | } 41 | 42 | func TestOpenFlagsAccmodeMaskWriteOnly(t *testing.T) { 43 | var f = fuse.OpenFlags(os.O_WRONLY | os.O_SYNC) 44 | if g, e := f&fuse.OpenAccessModeMask, fuse.OpenWriteOnly; g != e { 45 | t.Fatalf("OpenAccessModeMask behaves wrong: %v: %o != %o", f, g, e) 46 | } 47 | if f.IsReadOnly() { 48 | t.Fatalf("IsReadOnly is wrong: %v", f) 49 | } 50 | if !f.IsWriteOnly() { 51 | t.Fatalf("IsWriteOnly is wrong: %v", f) 52 | } 53 | if f.IsReadWrite() { 54 | t.Fatalf("IsReadWrite is wrong: %v", f) 55 | } 56 | } 57 | 58 | func TestOpenFlagsString(t *testing.T) { 59 | var f = fuse.OpenFlags(os.O_RDWR | os.O_SYNC | os.O_APPEND) 60 | if g, e := f.String(), "OpenReadWrite+OpenAppend+OpenSync"; g != e { 61 | t.Fatalf("OpenFlags.String: %q != %q", g, e) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /fuse_linux.go: -------------------------------------------------------------------------------- 1 | package fuse 2 | 3 | // Maximum file write size we are prepared to receive from the kernel. 4 | // 5 | // Linux 4.2.0 has been observed to cap this value at 128kB 6 | // (FUSE_MAX_PAGES_PER_REQ=32, 4kB pages). 7 | const maxWrite = 128 * 1024 8 | -------------------------------------------------------------------------------- /fuseutil/fuseutil.go: -------------------------------------------------------------------------------- 1 | package fuseutil // import "github.com/seaweedfs/fuse/fuseutil" 2 | 3 | import ( 4 | "github.com/seaweedfs/fuse" 5 | ) 6 | 7 | // HandleRead handles a read request assuming that data is the entire file content. 8 | // It adjusts the amount returned in resp according to req.Offset and req.Size. 9 | func HandleRead(req *fuse.ReadRequest, resp *fuse.ReadResponse, data []byte) { 10 | if req.Offset >= int64(len(data)) { 11 | data = nil 12 | } else { 13 | data = data[req.Offset:] 14 | } 15 | if len(data) > req.Size { 16 | data = data[:req.Size] 17 | } 18 | n := copy(resp.Data[:req.Size], data) 19 | resp.Data = resp.Data[:n] 20 | } 21 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/seaweedfs/fuse 2 | 3 | go 1.17 4 | 5 | require golang.org/x/sys v0.0.0-20210817142637-7d9622a276b7 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f h1:gWF768j/LaZugp8dyS4UwsslYCYz9XgFxvlgsn0n9H8= 2 | golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 3 | golang.org/x/sys v0.0.0-20210817142637-7d9622a276b7 h1:lQ8Btl/sJr2+f4ql7ffKUKfnV0BsgsICvm0oEeINAQY= 4 | golang.org/x/sys v0.0.0-20210817142637-7d9622a276b7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 5 | -------------------------------------------------------------------------------- /mount.go: -------------------------------------------------------------------------------- 1 | package fuse 2 | 3 | import ( 4 | "bufio" 5 | "errors" 6 | "io" 7 | "log" 8 | "sync" 9 | ) 10 | 11 | var ( 12 | // ErrOSXFUSENotFound is returned from Mount when the OSXFUSE 13 | // installation is not detected. 14 | // 15 | // Only happens on OS X. Make sure OSXFUSE is installed, or see 16 | // OSXFUSELocations for customization. 17 | ErrOSXFUSENotFound = errors.New("cannot locate OSXFUSE") 18 | ) 19 | 20 | func neverIgnoreLine(line string) bool { 21 | return false 22 | } 23 | 24 | func lineLogger(wg *sync.WaitGroup, prefix string, ignore func(line string) bool, r io.ReadCloser) { 25 | defer wg.Done() 26 | 27 | scanner := bufio.NewScanner(r) 28 | for scanner.Scan() { 29 | line := scanner.Text() 30 | if ignore(line) { 31 | continue 32 | } 33 | log.Printf("%s: %s", prefix, line) 34 | } 35 | if err := scanner.Err(); err != nil { 36 | log.Printf("%s, error reading: %v", prefix, err) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /mount_darwin.go: -------------------------------------------------------------------------------- 1 | package fuse 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "os" 8 | "os/exec" 9 | "path" 10 | "strconv" 11 | "strings" 12 | "sync" 13 | "syscall" 14 | "unsafe" 15 | ) 16 | 17 | var ( 18 | errNoAvail = errors.New("no available fuse devices") 19 | errNotLoaded = errors.New("osxfuse is not loaded") 20 | ) 21 | 22 | func loadOSXFUSE(bin string) error { 23 | cmd := exec.Command(bin) 24 | cmd.Dir = "/" 25 | cmd.Stdout = os.Stdout 26 | cmd.Stderr = os.Stderr 27 | err := cmd.Run() 28 | return err 29 | } 30 | 31 | func openOSXFUSEDev(devPrefix string) (*os.File, error) { 32 | var f *os.File 33 | var err error 34 | for i := uint64(0); ; i++ { 35 | path := devPrefix + strconv.FormatUint(i, 10) 36 | f, err = os.OpenFile(path, os.O_RDWR, 0000) 37 | if os.IsNotExist(err) { 38 | if i == 0 { 39 | // not even the first device was found -> fuse is not loaded 40 | return nil, errNotLoaded 41 | } 42 | 43 | // we've run out of kernel-provided devices 44 | return nil, errNoAvail 45 | } 46 | 47 | if err2, ok := err.(*os.PathError); ok && err2.Err == syscall.EBUSY { 48 | // try the next one 49 | continue 50 | } 51 | 52 | if err != nil { 53 | return nil, err 54 | } 55 | return f, nil 56 | } 57 | } 58 | 59 | func handleMountOSXFUSE(helperName string, errCh chan<- error) func(line string) (ignore bool) { 60 | var noMountpointPrefix = helperName + `: ` 61 | const noMountpointSuffix = `: No such file or directory` 62 | return func(line string) (ignore bool) { 63 | if strings.HasPrefix(line, noMountpointPrefix) && strings.HasSuffix(line, noMountpointSuffix) { 64 | // re-extract it from the error message in case some layer 65 | // changed the path 66 | mountpoint := line[len(noMountpointPrefix) : len(line)-len(noMountpointSuffix)] 67 | err := &MountpointDoesNotExistError{ 68 | Path: mountpoint, 69 | } 70 | select { 71 | case errCh <- err: 72 | return true 73 | default: 74 | // not the first error; fall back to logging it 75 | return false 76 | } 77 | } 78 | 79 | return false 80 | } 81 | } 82 | 83 | // isBoringMountOSXFUSEError returns whether the Wait error is 84 | // uninteresting; exit status 64 is. 85 | func isBoringMountOSXFUSEError(err error) bool { 86 | if err, ok := err.(*exec.ExitError); ok && err.Exited() { 87 | if status, ok := err.Sys().(syscall.WaitStatus); ok && status.ExitStatus() == 64 { 88 | return true 89 | } 90 | } 91 | return false 92 | } 93 | 94 | func callMount(bin string, daemonVar string, dir string, conf *mountConfig, f *os.File, ready chan<- struct{}, errp *error) error { 95 | for k, v := range conf.options { 96 | if strings.Contains(k, ",") || strings.Contains(v, ",") { 97 | // Silly limitation but the mount helper does not 98 | // understand any escaping. See TestMountOptionCommaError. 99 | return fmt.Errorf("mount options cannot contain commas on darwin: %q=%q", k, v) 100 | } 101 | } 102 | cmd := exec.Command( 103 | bin, 104 | "-o", conf.getOptions(), 105 | // Tell osxfuse-kext how large our buffer is. It must split 106 | // writes larger than this into multiple writes. 107 | // 108 | // OSXFUSE seems to ignore InitResponse.MaxWrite, and uses 109 | // this instead. 110 | "-o", "iosize="+strconv.FormatUint(maxWrite, 10), 111 | // refers to fd passed in cmd.ExtraFiles 112 | "3", 113 | dir, 114 | ) 115 | cmd.ExtraFiles = []*os.File{f} 116 | cmd.Env = os.Environ() 117 | // OSXFUSE <3.3.0 118 | cmd.Env = append(cmd.Env, "MOUNT_FUSEFS_CALL_BY_LIB=") 119 | // OSXFUSE >=3.3.0 120 | cmd.Env = append(cmd.Env, "MOUNT_OSXFUSE_CALL_BY_LIB=") 121 | 122 | daemon := os.Args[0] 123 | if daemonVar != "" { 124 | cmd.Env = append(cmd.Env, daemonVar+"="+daemon) 125 | } 126 | 127 | stdout, err := cmd.StdoutPipe() 128 | if err != nil { 129 | return fmt.Errorf("setting up mount_osxfusefs stderr: %v", err) 130 | } 131 | stderr, err := cmd.StderrPipe() 132 | if err != nil { 133 | return fmt.Errorf("setting up mount_osxfusefs stderr: %v", err) 134 | } 135 | 136 | if err := cmd.Start(); err != nil { 137 | return fmt.Errorf("mount_osxfusefs: %v", err) 138 | } 139 | helperErrCh := make(chan error, 1) 140 | go func() { 141 | var wg sync.WaitGroup 142 | wg.Add(2) 143 | go lineLogger(&wg, "mount helper output", neverIgnoreLine, stdout) 144 | helperName := path.Base(bin) 145 | go lineLogger(&wg, "mount helper error", handleMountOSXFUSE(helperName, helperErrCh), stderr) 146 | wg.Wait() 147 | if err := cmd.Wait(); err != nil { 148 | // see if we have a better error to report 149 | select { 150 | case helperErr := <-helperErrCh: 151 | // log the Wait error if it's not what we expected 152 | if !isBoringMountOSXFUSEError(err) { 153 | log.Printf("mount helper failed: %v", err) 154 | } 155 | // and now return what we grabbed from stderr as the real 156 | // error 157 | *errp = helperErr 158 | close(ready) 159 | return 160 | default: 161 | // nope, fall back to generic message 162 | } 163 | 164 | *errp = fmt.Errorf("mount_osxfusefs: %v", err) 165 | close(ready) 166 | return 167 | } 168 | 169 | *errp = nil 170 | close(ready) 171 | }() 172 | return nil 173 | } 174 | 175 | func mount(dir string, conf *mountConfig, ready chan<- struct{}, errp *error) (*os.File, error) { 176 | locations := conf.osxfuseLocations 177 | if locations == nil { 178 | locations = []OSXFUSEPaths{ 179 | OSXFUSELocationV3, 180 | OSXFUSELocationV2, 181 | } 182 | } 183 | for _, loc := range locations { 184 | if _, err := os.Stat(loc.Mount); os.IsNotExist(err) { 185 | // try the other locations 186 | continue 187 | } 188 | 189 | f, err := openOSXFUSEDev(loc.DevicePrefix) 190 | if err == errNotLoaded { 191 | err = loadOSXFUSE(loc.Load) 192 | if err != nil { 193 | return nil, err 194 | } 195 | // try again 196 | f, err = openOSXFUSEDev(loc.DevicePrefix) 197 | } 198 | if err != nil { 199 | return nil, err 200 | } 201 | err = callMount(loc.Mount, loc.DaemonVar, dir, conf, f, ready, errp) 202 | if err != nil { 203 | f.Close() 204 | return nil, err 205 | } 206 | return f, nil 207 | } 208 | 209 | // macfuse v4 210 | locations = []OSXFUSEPaths{ 211 | OSXFUSELocationV4, 212 | } 213 | for _, loc := range locations { 214 | if _, err := os.Stat(loc.Mount); os.IsNotExist(err) { 215 | // try the other locations 216 | continue 217 | } 218 | 219 | local, remote, err := unixgramSocketpair() 220 | if err != nil { 221 | return nil, fmt.Errorf("create socket pair: %v", err) 222 | } 223 | 224 | defer local.Close() 225 | defer remote.Close() 226 | 227 | cmd := exec.Command(loc.Mount, 228 | "-o", conf.getOptions(), 229 | "-o", "iosize="+strconv.FormatUint(maxWrite, 10), 230 | dir) 231 | cmd.ExtraFiles = []*os.File{remote} // fd would be (index + 3) 232 | cmd.Env = append(os.Environ(), 233 | "_FUSE_CALL_BY_LIB=", 234 | "_FUSE_DAEMON_PATH="+os.Args[0], 235 | "_FUSE_COMMFD=3", 236 | "_FUSE_COMMVERS=2", 237 | "MOUNT_OSXFUSE_CALL_BY_LIB=", 238 | "MOUNT_OSXFUSE_DAEMON_PATH="+os.Args[0]) 239 | 240 | stdout, err := cmd.StdoutPipe() 241 | if err != nil { 242 | return nil, fmt.Errorf("setting up mount_osxfusefs stderr: %v", err) 243 | } 244 | stderr, err := cmd.StderrPipe() 245 | if err != nil { 246 | return nil, fmt.Errorf("setting up mount_osxfusefs stderr: %v", err) 247 | } 248 | 249 | if err = cmd.Start(); err != nil { 250 | return nil, fmt.Errorf("cmd start: %v", err) 251 | } 252 | 253 | fd, err := getConnection(local) 254 | if err != nil { 255 | return nil, err 256 | } 257 | syscall.CloseOnExec(fd) 258 | 259 | helperErrCh := make(chan error, 1) 260 | go func() { 261 | var wg sync.WaitGroup 262 | wg.Add(2) 263 | go lineLogger(&wg, "mount helper output", neverIgnoreLine, stdout) 264 | helperName := path.Base(loc.Mount) 265 | go lineLogger(&wg, "mount helper error", handleMountOSXFUSE(helperName, helperErrCh), stderr) 266 | wg.Wait() 267 | // wait inside a goroutine or otherwise it would block forever for unknown reasons 268 | if err := cmd.Wait(); err != nil { 269 | // see if we have a better error to report 270 | select { 271 | case helperErr := <-helperErrCh: 272 | // log the Wait error if it's not what we expected 273 | if !isBoringMountOSXFUSEError(err) { 274 | log.Printf("mount helper failed: %v", err) 275 | } 276 | // and now return what we grabbed from stderr as the real 277 | // error 278 | *errp = helperErr 279 | close(ready) 280 | return 281 | default: 282 | // nope, fall back to generic message 283 | } 284 | *errp = fmt.Errorf("mount_osxfusefs: %v", err) 285 | close(ready) 286 | return 287 | } 288 | *errp = nil 289 | close(ready) 290 | 291 | return 292 | }() 293 | 294 | return os.NewFile(uintptr(fd), "conn"), nil 295 | } 296 | 297 | return nil, ErrOSXFUSENotFound 298 | } 299 | 300 | // based on https://github.com/hanwen/go-fuse/commit/09a3c381714cf1011fb2d08885f29896cd496a0c 301 | func unixgramSocketpair() (l, r *os.File, err error) { 302 | fd, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0) 303 | if err != nil { 304 | return nil, nil, os.NewSyscallError("socketpair", 305 | err.(syscall.Errno)) 306 | } 307 | l = os.NewFile(uintptr(fd[0]), "socketpair-half1") 308 | r = os.NewFile(uintptr(fd[1]), "socketpair-half2") 309 | return 310 | } 311 | 312 | func getConnection(local *os.File) (int, error) { 313 | var data [4]byte 314 | control := make([]byte, 4*256) 315 | 316 | // n, oobn, recvflags, from, errno - todo: error checking. 317 | _, oobn, _, _, 318 | err := syscall.Recvmsg( 319 | int(local.Fd()), data[:], control[:], 0) 320 | if err != nil { 321 | return 0, err 322 | } 323 | 324 | message := *(*syscall.Cmsghdr)(unsafe.Pointer(&control[0])) 325 | fd := *(*int32)(unsafe.Pointer(uintptr(unsafe.Pointer(&control[0])) + syscall.SizeofCmsghdr)) 326 | 327 | if message.Type != syscall.SCM_RIGHTS { 328 | return 0, fmt.Errorf("getConnection: recvmsg returned wrong control type: %d", message.Type) 329 | } 330 | if oobn <= syscall.SizeofCmsghdr { 331 | return 0, fmt.Errorf("getConnection: too short control message. Length: %d", oobn) 332 | } 333 | if fd < 0 { 334 | return 0, fmt.Errorf("getConnection: fd < 0: %d", fd) 335 | } 336 | return int(fd), nil 337 | } -------------------------------------------------------------------------------- /mount_freebsd.go: -------------------------------------------------------------------------------- 1 | package fuse 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "os/exec" 8 | "strings" 9 | "sync" 10 | "syscall" 11 | ) 12 | 13 | func handleMountFusefsStderr(errCh chan<- error) func(line string) (ignore bool) { 14 | return func(line string) (ignore bool) { 15 | const ( 16 | noMountpointPrefix = `mount_fusefs: ` 17 | noMountpointSuffix = `: No such file or directory` 18 | ) 19 | if strings.HasPrefix(line, noMountpointPrefix) && strings.HasSuffix(line, noMountpointSuffix) { 20 | // re-extract it from the error message in case some layer 21 | // changed the path 22 | mountpoint := line[len(noMountpointPrefix) : len(line)-len(noMountpointSuffix)] 23 | err := &MountpointDoesNotExistError{ 24 | Path: mountpoint, 25 | } 26 | select { 27 | case errCh <- err: 28 | return true 29 | default: 30 | // not the first error; fall back to logging it 31 | return false 32 | } 33 | } 34 | 35 | return false 36 | } 37 | } 38 | 39 | // isBoringMountFusefsError returns whether the Wait error is 40 | // uninteresting; exit status 1 is. 41 | func isBoringMountFusefsError(err error) bool { 42 | if err, ok := err.(*exec.ExitError); ok && err.Exited() { 43 | if status, ok := err.Sys().(syscall.WaitStatus); ok && status.ExitStatus() == 1 { 44 | return true 45 | } 46 | } 47 | return false 48 | } 49 | 50 | func mount(dir string, conf *mountConfig, ready chan<- struct{}, errp *error) (*os.File, error) { 51 | for k, v := range conf.options { 52 | if strings.Contains(k, ",") || strings.Contains(v, ",") { 53 | // Silly limitation but the mount helper does not 54 | // understand any escaping. See TestMountOptionCommaError. 55 | return nil, fmt.Errorf("mount options cannot contain commas on FreeBSD: %q=%q", k, v) 56 | } 57 | } 58 | 59 | f, err := os.OpenFile("/dev/fuse", os.O_RDWR, 0000) 60 | if err != nil { 61 | *errp = err 62 | return nil, err 63 | } 64 | 65 | cmd := exec.Command( 66 | "/sbin/mount_fusefs", 67 | "--safe", 68 | "-o", conf.getOptions(), 69 | "3", 70 | dir, 71 | ) 72 | cmd.ExtraFiles = []*os.File{f} 73 | 74 | stdout, err := cmd.StdoutPipe() 75 | if err != nil { 76 | return nil, fmt.Errorf("setting up mount_fusefs stderr: %v", err) 77 | } 78 | stderr, err := cmd.StderrPipe() 79 | if err != nil { 80 | return nil, fmt.Errorf("setting up mount_fusefs stderr: %v", err) 81 | } 82 | 83 | if err := cmd.Start(); err != nil { 84 | return nil, fmt.Errorf("mount_fusefs: %v", err) 85 | } 86 | helperErrCh := make(chan error, 1) 87 | var wg sync.WaitGroup 88 | wg.Add(2) 89 | go lineLogger(&wg, "mount helper output", neverIgnoreLine, stdout) 90 | go lineLogger(&wg, "mount helper error", handleMountFusefsStderr(helperErrCh), stderr) 91 | wg.Wait() 92 | if err := cmd.Wait(); err != nil { 93 | // see if we have a better error to report 94 | select { 95 | case helperErr := <-helperErrCh: 96 | // log the Wait error if it's not what we expected 97 | if !isBoringMountFusefsError(err) { 98 | log.Printf("mount helper failed: %v", err) 99 | } 100 | // and now return what we grabbed from stderr as the real 101 | // error 102 | return nil, helperErr 103 | default: 104 | // nope, fall back to generic message 105 | } 106 | return nil, fmt.Errorf("mount_fusefs: %v", err) 107 | } 108 | 109 | close(ready) 110 | return f, nil 111 | } 112 | -------------------------------------------------------------------------------- /mount_linux.go: -------------------------------------------------------------------------------- 1 | package fuse 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net" 7 | "os" 8 | "os/exec" 9 | "strings" 10 | "sync" 11 | "syscall" 12 | ) 13 | 14 | func handleFusermountStderr(errCh chan<- error) func(line string) (ignore bool) { 15 | return func(line string) (ignore bool) { 16 | if line == `fusermount: failed to open /etc/fuse.conf: Permission denied` { 17 | // Silence this particular message, it occurs way too 18 | // commonly and isn't very relevant to whether the mount 19 | // succeeds or not. 20 | return true 21 | } 22 | 23 | const ( 24 | noMountpointPrefix = `fusermount: failed to access mountpoint ` 25 | noMountpointSuffix = `: No such file or directory` 26 | ) 27 | if strings.HasPrefix(line, noMountpointPrefix) && strings.HasSuffix(line, noMountpointSuffix) { 28 | // re-extract it from the error message in case some layer 29 | // changed the path 30 | mountpoint := line[len(noMountpointPrefix) : len(line)-len(noMountpointSuffix)] 31 | err := &MountpointDoesNotExistError{ 32 | Path: mountpoint, 33 | } 34 | select { 35 | case errCh <- err: 36 | return true 37 | default: 38 | // not the first error; fall back to logging it 39 | return false 40 | } 41 | } 42 | 43 | return false 44 | } 45 | } 46 | 47 | // isBoringFusermountError returns whether the Wait error is 48 | // uninteresting; exit status 1 is. 49 | func isBoringFusermountError(err error) bool { 50 | if err, ok := err.(*exec.ExitError); ok && err.Exited() { 51 | if status, ok := err.Sys().(syscall.WaitStatus); ok && status.ExitStatus() == 1 { 52 | return true 53 | } 54 | } 55 | return false 56 | } 57 | 58 | func mountdev(dir string, conf *mountConfig) (*os.File, error) { 59 | f, err := os.OpenFile("/dev/fuse", os.O_RDWR, 0) 60 | if err != nil { 61 | return nil, err 62 | } 63 | 64 | // no known use for conf in the system call *yet*. It took a while to find 65 | // out what combination of strings would get past EINVAL. 66 | data := fmt.Sprintf("fd=%d,rootmode=40000,user_id=0,group_id=0", f.Fd()) 67 | mp := conf.options["fsname"] 68 | st := conf.options["subtype"] 69 | t := "fuse" 70 | if st != "" { 71 | t += "." + st 72 | } 73 | err = syscall.Mount(mp, dir, t, syscall.MS_NOSUID|syscall.MS_NODEV, data) 74 | if err != nil { 75 | return nil, err 76 | } 77 | return f, nil 78 | } 79 | 80 | func mount(dir string, conf *mountConfig, ready chan<- struct{}, errp *error) (fusefd *os.File, err error) { 81 | // linux mount is never delayed 82 | close(ready) 83 | 84 | // if os.Getuid == 0, try mountdev. That could fail for all sorts of reasons, 85 | // and if it does, just keep on this other path. 86 | /* 87 | if os.Getuid() == 0 { 88 | f, err := mountdev(dir, conf) 89 | if err == nil { 90 | return f, err 91 | } 92 | log.Printf("mount: mountdev failed with %v, trying fusermount", err) 93 | } 94 | */ 95 | 96 | fds, err := syscall.Socketpair(syscall.AF_FILE, syscall.SOCK_STREAM, 0) 97 | if err != nil { 98 | return nil, fmt.Errorf("socketpair error: %v", err) 99 | } 100 | 101 | writeFile := os.NewFile(uintptr(fds[0]), "fusermount-child-writes") 102 | defer writeFile.Close() 103 | 104 | readFile := os.NewFile(uintptr(fds[1]), "fusermount-parent-reads") 105 | defer readFile.Close() 106 | 107 | cmd := exec.Command( 108 | "fusermount", 109 | "-o", conf.getOptions(), 110 | "--", 111 | dir, 112 | ) 113 | cmd.Env = append(os.Environ(), "_FUSE_COMMFD=3") 114 | 115 | cmd.ExtraFiles = []*os.File{writeFile} 116 | 117 | var wg sync.WaitGroup 118 | stdout, err := cmd.StdoutPipe() 119 | if err != nil { 120 | return nil, fmt.Errorf("setting up fusermount stderr: %v", err) 121 | } 122 | stderr, err := cmd.StderrPipe() 123 | if err != nil { 124 | return nil, fmt.Errorf("setting up fusermount stderr: %v", err) 125 | } 126 | 127 | if err := cmd.Start(); err != nil { 128 | return nil, fmt.Errorf("fusermount: %v", err) 129 | } 130 | helperErrCh := make(chan error, 1) 131 | wg.Add(2) 132 | go lineLogger(&wg, "mount helper output", neverIgnoreLine, stdout) 133 | go lineLogger(&wg, "mount helper error", handleFusermountStderr(helperErrCh), stderr) 134 | wg.Wait() 135 | if err := cmd.Wait(); err != nil { 136 | // see if we have a better error to report 137 | select { 138 | case helperErr := <-helperErrCh: 139 | // log the Wait error if it's not what we expected 140 | if !isBoringFusermountError(err) { 141 | log.Printf("mount helper failed: %v", err) 142 | } 143 | // and now return what we grabbed from stderr as the real 144 | // error 145 | return nil, helperErr 146 | default: 147 | // nope, fall back to generic message 148 | } 149 | 150 | return nil, fmt.Errorf("fusermount: %v", err) 151 | } 152 | 153 | c, err := net.FileConn(readFile) 154 | if err != nil { 155 | return nil, fmt.Errorf("FileConn from fusermount socket: %v", err) 156 | } 157 | defer c.Close() 158 | 159 | uc, ok := c.(*net.UnixConn) 160 | if !ok { 161 | return nil, fmt.Errorf("unexpected FileConn type; expected UnixConn, got %T", c) 162 | } 163 | 164 | buf := make([]byte, 32) // expect 1 byte 165 | oob := make([]byte, 32) // expect 24 bytes 166 | _, oobn, _, _, err := uc.ReadMsgUnix(buf, oob) 167 | scms, err := syscall.ParseSocketControlMessage(oob[:oobn]) 168 | if err != nil { 169 | return nil, fmt.Errorf("ParseSocketControlMessage: %v", err) 170 | } 171 | if len(scms) != 1 { 172 | return nil, fmt.Errorf("expected 1 SocketControlMessage; got scms = %#v", scms) 173 | } 174 | scm := scms[0] 175 | gotFds, err := syscall.ParseUnixRights(&scm) 176 | if err != nil { 177 | return nil, fmt.Errorf("syscall.ParseUnixRights: %v", err) 178 | } 179 | if len(gotFds) != 1 { 180 | return nil, fmt.Errorf("wanted 1 fd; got %#v", gotFds) 181 | } 182 | f := os.NewFile(uintptr(gotFds[0]), "/dev/fuse") 183 | return f, nil 184 | } 185 | -------------------------------------------------------------------------------- /options.go: -------------------------------------------------------------------------------- 1 | package fuse 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | ) 7 | 8 | func dummyOption(conf *mountConfig) error { 9 | return nil 10 | } 11 | 12 | // mountConfig holds the configuration for a mount operation. 13 | // Use it by passing MountOption values to Mount. 14 | type mountConfig struct { 15 | options map[string]string 16 | maxReadahead uint32 17 | initFlags InitFlags 18 | maxBackground uint16 19 | congestionThreshold uint16 20 | osxfuseLocations []OSXFUSEPaths 21 | } 22 | 23 | func escapeComma(s string) string { 24 | s = strings.Replace(s, `\`, `\\`, -1) 25 | s = strings.Replace(s, `,`, `\,`, -1) 26 | return s 27 | } 28 | 29 | // getOptions makes a string of options suitable for passing to FUSE 30 | // mount flag `-o`. Returns an empty string if no options were set. 31 | // Any platform specific adjustments should happen before the call. 32 | func (m *mountConfig) getOptions() string { 33 | var opts []string 34 | for k, v := range m.options { 35 | k = escapeComma(k) 36 | if v != "" { 37 | k += "=" + escapeComma(v) 38 | } 39 | opts = append(opts, k) 40 | } 41 | return strings.Join(opts, ",") 42 | } 43 | 44 | type mountOption func(*mountConfig) error 45 | 46 | // MountOption is passed to Mount to change the behavior of the mount. 47 | type MountOption mountOption 48 | 49 | // FSName sets the file system name (also called source) that is 50 | // visible in the list of mounted file systems. 51 | // 52 | // FreeBSD ignores this option. 53 | func FSName(name string) MountOption { 54 | return func(conf *mountConfig) error { 55 | conf.options["fsname"] = name 56 | return nil 57 | } 58 | } 59 | 60 | // Subtype sets the subtype of the mount. The main type is always 61 | // `fuse`. The type in a list of mounted file systems will look like 62 | // `fuse.foo`. 63 | // 64 | // OS X ignores this option. 65 | // FreeBSD ignores this option. 66 | func Subtype(fstype string) MountOption { 67 | return func(conf *mountConfig) error { 68 | conf.options["subtype"] = fstype 69 | return nil 70 | } 71 | } 72 | 73 | // AutoXattr makes osxfuse automatically handle xattr operations using "._" 74 | // prefixed files. 75 | // 76 | // OS X only. Others ignore this option. 77 | func AutoXattr() MountOption { 78 | return autoXattr 79 | } 80 | 81 | // LocalVolume sets the volume to be local (instead of network), 82 | // changing the behavior of Finder, Spotlight, and such. 83 | // 84 | // OS X only. Others ignore this option. 85 | func LocalVolume() MountOption { 86 | return localVolume 87 | } 88 | 89 | // VolumeName sets the volume name shown in Finder. 90 | // 91 | // OS X only. Others ignore this option. 92 | func VolumeName(name string) MountOption { 93 | return volumeName(name) 94 | } 95 | 96 | // NoAppleDouble makes OSXFUSE disallow files with names used by OS X 97 | // to store extended attributes on file systems that do not support 98 | // them natively. 99 | // 100 | // Such file names are: 101 | // 102 | // ._* 103 | // .DS_Store 104 | // 105 | // OS X only. Others ignore this option. 106 | func NoAppleDouble() MountOption { 107 | return noAppleDouble 108 | } 109 | 110 | // NoAppleXattr makes OSXFUSE disallow extended attributes with the 111 | // prefix "com.apple.". This disables persistent Finder state and 112 | // other such information. 113 | // 114 | // OS X only. Others ignore this option. 115 | func NoAppleXattr() MountOption { 116 | return noAppleXattr 117 | } 118 | 119 | // NoBrowse makes OSXFUSE mark the volume as non-browsable, so that 120 | // Finder won't automatically browse it. 121 | // 122 | // OS X only. Others ignore this option. 123 | func NoBrowse() MountOption { 124 | return noBrowse 125 | } 126 | 127 | // ExclCreate causes O_EXCL flag to be set for only "truly" exclusive creates, 128 | // i.e. create calls for which the initiator explicitly set the O_EXCL flag. 129 | // 130 | // OSXFUSE expects all create calls to return EEXIST in case the file 131 | // already exists, regardless of whether O_EXCL was specified or not. 132 | // To ensure this behavior, it normally sets OpenExclusive for all 133 | // Create calls, regardless of whether the original call had it set. 134 | // For distributed filesystems, that may force every file create to be 135 | // a distributed consensus action, causing undesirable delays. 136 | // 137 | // This option makes the FUSE filesystem see the original flag value, 138 | // and better decide when to ensure global consensus. 139 | // 140 | // Note that returning EEXIST on existing file create is still 141 | // expected with OSXFUSE, regardless of the presence of the 142 | // OpenExclusive flag. 143 | // 144 | // For more information, see 145 | // https://github.com/osxfuse/osxfuse/issues/209 146 | // 147 | // OS X only. Others ignore this options. 148 | // Requires OSXFUSE 3.4.1 or newer. 149 | func ExclCreate() MountOption { 150 | return exclCreate 151 | } 152 | 153 | // DaemonTimeout sets the time in seconds between a request and a reply before 154 | // the FUSE mount is declared dead. 155 | // 156 | // OS X only. Others ignore this option. 157 | func DaemonTimeout(name string) MountOption { 158 | return daemonTimeout(name) 159 | } 160 | 161 | var ErrCannotCombineAllowOtherAndAllowRoot = errors.New("cannot combine AllowOther and AllowRoot") 162 | 163 | // AllowOther allows other users to access the file system. 164 | // 165 | // Only one of AllowOther or AllowRoot can be used. 166 | func AllowOther() MountOption { 167 | return func(conf *mountConfig) error { 168 | if _, ok := conf.options["allow_root"]; ok { 169 | return ErrCannotCombineAllowOtherAndAllowRoot 170 | } 171 | conf.options["allow_other"] = "" 172 | return nil 173 | } 174 | } 175 | 176 | // AllowRoot allows other users to access the file system. 177 | // 178 | // Only one of AllowOther or AllowRoot can be used. 179 | // 180 | // FreeBSD ignores this option. 181 | func AllowRoot() MountOption { 182 | return func(conf *mountConfig) error { 183 | if _, ok := conf.options["allow_other"]; ok { 184 | return ErrCannotCombineAllowOtherAndAllowRoot 185 | } 186 | conf.options["allow_root"] = "" 187 | return nil 188 | } 189 | } 190 | 191 | // AllowDev enables interpreting character or block special devices on the 192 | // filesystem. 193 | func AllowDev() MountOption { 194 | return func(conf *mountConfig) error { 195 | conf.options["dev"] = "" 196 | return nil 197 | } 198 | } 199 | 200 | // AllowSUID allows set-user-identifier or set-group-identifier bits to take 201 | // effect. 202 | func AllowSUID() MountOption { 203 | return func(conf *mountConfig) error { 204 | conf.options["suid"] = "" 205 | return nil 206 | } 207 | } 208 | 209 | // DefaultPermissions makes the kernel enforce access control based on 210 | // the file mode (as in chmod). 211 | // 212 | // Without this option, the Node itself decides what is and is not 213 | // allowed. This is normally ok because FUSE file systems cannot be 214 | // accessed by other users without AllowOther/AllowRoot. 215 | // 216 | // FreeBSD ignores this option. 217 | func DefaultPermissions() MountOption { 218 | return func(conf *mountConfig) error { 219 | conf.options["default_permissions"] = "" 220 | return nil 221 | } 222 | } 223 | 224 | // ReadOnly makes the mount read-only. 225 | func ReadOnly() MountOption { 226 | return func(conf *mountConfig) error { 227 | conf.options["ro"] = "" 228 | return nil 229 | } 230 | } 231 | 232 | // MaxReadahead sets the number of bytes that can be prefetched for 233 | // sequential reads. The kernel can enforce a maximum value lower than 234 | // this. 235 | // 236 | // This setting makes the kernel perform speculative reads that do not 237 | // originate from any client process. This usually tremendously 238 | // improves read performance. 239 | func MaxReadahead(n uint32) MountOption { 240 | return func(conf *mountConfig) error { 241 | conf.maxReadahead = n 242 | return nil 243 | } 244 | } 245 | 246 | // AsyncRead enables multiple outstanding read requests for the same 247 | // handle. Without this, there is at most one request in flight at a 248 | // time. 249 | func AsyncRead() MountOption { 250 | return func(conf *mountConfig) error { 251 | conf.initFlags |= InitAsyncRead 252 | return nil 253 | } 254 | } 255 | 256 | // WritebackCache enables the kernel to buffer writes before sending 257 | // them to the FUSE server. Without this, writethrough caching is 258 | // used. 259 | func WritebackCache() MountOption { 260 | return func(conf *mountConfig) error { 261 | conf.initFlags |= InitWritebackCache 262 | return nil 263 | } 264 | } 265 | 266 | // OSXFUSEPaths describes the paths used by an installed OSXFUSE 267 | // version. See OSXFUSELocationV3 for typical values. 268 | type OSXFUSEPaths struct { 269 | // Prefix for the device file. At mount time, an incrementing 270 | // number is suffixed until a free FUSE device is found. 271 | DevicePrefix string 272 | // Path of the load helper, used to load the kernel extension if 273 | // no device files are found. 274 | Load string 275 | // Path of the mount helper, used for the actual mount operation. 276 | Mount string 277 | // Environment variable used to pass the path to the executable 278 | // calling the mount helper. 279 | DaemonVar string 280 | } 281 | 282 | // Default paths for OSXFUSE. See OSXFUSELocations. 283 | var ( 284 | OSXFUSELocationV4 = OSXFUSEPaths{ 285 | DevicePrefix: "/dev/macfuse", 286 | Load: "/Library/Filesystems/macfuse.fs/Contents/Resources/load_macfuse", 287 | Mount: "/Library/Filesystems/macfuse.fs/Contents/Resources/mount_macfuse", 288 | DaemonVar: "MOUNT_OSXFUSE_DAEMON_PATH", 289 | } 290 | OSXFUSELocationV3 = OSXFUSEPaths{ 291 | DevicePrefix: "/dev/osxfuse", 292 | Load: "/Library/Filesystems/osxfuse.fs/Contents/Resources/load_osxfuse", 293 | Mount: "/Library/Filesystems/osxfuse.fs/Contents/Resources/mount_osxfuse", 294 | DaemonVar: "MOUNT_OSXFUSE_DAEMON_PATH", 295 | } 296 | OSXFUSELocationV2 = OSXFUSEPaths{ 297 | DevicePrefix: "/dev/osxfuse", 298 | Load: "/Library/Filesystems/osxfusefs.fs/Support/load_osxfusefs", 299 | Mount: "/Library/Filesystems/osxfusefs.fs/Support/mount_osxfusefs", 300 | DaemonVar: "MOUNT_FUSEFS_DAEMON_PATH", 301 | } 302 | ) 303 | 304 | // OSXFUSELocations sets where to look for OSXFUSE files. The 305 | // arguments are all the possible locations. The previous locations 306 | // are replaced. 307 | // 308 | // Without this option, OSXFUSELocationV3 and OSXFUSELocationV2 are 309 | // used. 310 | // 311 | // OS X only. Others ignore this option. 312 | func OSXFUSELocations(paths ...OSXFUSEPaths) MountOption { 313 | return func(conf *mountConfig) error { 314 | if len(paths) == 0 { 315 | return errors.New("must specify at least one location for OSXFUSELocations") 316 | } 317 | // replace previous values, but make a copy so there's no 318 | // worries about caller mutating their slice 319 | conf.osxfuseLocations = append(conf.osxfuseLocations[:0], paths...) 320 | return nil 321 | } 322 | } 323 | 324 | // AllowNonEmptyMount allows the mounting over a non-empty directory. 325 | // 326 | // The files in it will be shadowed by the freshly created mount. By 327 | // default these mounts are rejected to prevent accidental covering up 328 | // of data, which could for example prevent automatic backup. 329 | func AllowNonEmptyMount() MountOption { 330 | return func(conf *mountConfig) error { 331 | conf.options["nonempty"] = "" 332 | return nil 333 | } 334 | } 335 | 336 | // MaxBackground sets the maximum number of FUSE requests the kernel 337 | // will submit in the background. Background requests are used when an 338 | // immediate answer is not needed. This may help with request latency. 339 | // 340 | // On Linux, this can be adjusted on the fly with 341 | // /sys/fs/fuse/connections/CONN/max_background 342 | func MaxBackground(n uint16) MountOption { 343 | return func(conf *mountConfig) error { 344 | conf.maxBackground = n 345 | return nil 346 | } 347 | } 348 | 349 | // CongestionThreshold sets the number of outstanding background FUSE 350 | // requests beyond which the kernel considers the filesystem 351 | // congested. This may help with request latency. 352 | // 353 | // On Linux, this can be adjusted on the fly with 354 | // /sys/fs/fuse/connections/CONN/congestion_threshold 355 | func CongestionThreshold(n uint16) MountOption { 356 | // TODO to test this, we'd have to figure out our connection id 357 | // and read /sys 358 | return func(conf *mountConfig) error { 359 | conf.congestionThreshold = n 360 | return nil 361 | } 362 | } 363 | 364 | // LockingFlock enables flock-based (BSD) locking. This is mostly 365 | // useful for distributed filesystems with global locking. Without 366 | // this, kernel manages local locking automatically. 367 | func LockingFlock() MountOption { 368 | return func(conf *mountConfig) error { 369 | conf.initFlags |= InitFlockLocks 370 | return nil 371 | } 372 | } 373 | 374 | // LockingPOSIX enables flock-based (BSD) locking. This is mostly 375 | // useful for distributed filesystems with global locking. Without 376 | // this, kernel manages local locking automatically. 377 | // 378 | // Beware POSIX locks are a broken API with unintuitive behavior for 379 | // callers. 380 | func LockingPOSIX() MountOption { 381 | return func(conf *mountConfig) error { 382 | conf.initFlags |= InitPOSIXLocks 383 | return nil 384 | } 385 | } 386 | -------------------------------------------------------------------------------- /options_daemon_timeout_test.go: -------------------------------------------------------------------------------- 1 | // Test for adjustable timeout between a FUSE request and the daemon's response. 2 | // 3 | // +build darwin freebsd 4 | 5 | package fuse_test 6 | 7 | import ( 8 | "context" 9 | "os" 10 | "runtime" 11 | "syscall" 12 | "testing" 13 | "time" 14 | 15 | "github.com/seaweedfs/fuse" 16 | "github.com/seaweedfs/fuse/fs" 17 | "github.com/seaweedfs/fuse/fs/fstestutil" 18 | ) 19 | 20 | type slowCreaterDir struct { 21 | fstestutil.Dir 22 | } 23 | 24 | var _ fs.NodeCreater = slowCreaterDir{} 25 | 26 | func (c slowCreaterDir) Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (fs.Node, fs.Handle, error) { 27 | time.Sleep(10 * time.Second) 28 | // pick a really distinct error, to identify it later 29 | return nil, nil, fuse.Errno(syscall.ENAMETOOLONG) 30 | } 31 | 32 | func TestMountOptionDaemonTimeout(t *testing.T) { 33 | if runtime.GOOS != "darwin" && runtime.GOOS != "freebsd" { 34 | return 35 | } 36 | if testing.Short() { 37 | t.Skip("skipping time-based test in short mode") 38 | } 39 | t.Parallel() 40 | 41 | mnt, err := fstestutil.MountedT(t, 42 | fstestutil.SimpleFS{slowCreaterDir{}}, 43 | nil, 44 | fuse.DaemonTimeout("2"), 45 | ) 46 | if err != nil { 47 | t.Fatal(err) 48 | } 49 | defer mnt.Close() 50 | 51 | // This should fail by the kernel timing out the request. 52 | f, err := os.Create(mnt.Dir + "/child") 53 | if err == nil { 54 | f.Close() 55 | t.Fatal("expected an error") 56 | } 57 | perr, ok := err.(*os.PathError) 58 | if !ok { 59 | t.Fatalf("expected PathError, got %T: %v", err, err) 60 | } 61 | if perr.Err == syscall.ENAMETOOLONG { 62 | t.Fatalf("expected other than ENAMETOOLONG, got %T: %v", err, err) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /options_darwin.go: -------------------------------------------------------------------------------- 1 | package fuse 2 | 3 | func autoXattr(conf *mountConfig) error { 4 | conf.options["auto_xattr"] = "" 5 | return nil 6 | } 7 | 8 | func localVolume(conf *mountConfig) error { 9 | conf.options["local"] = "" 10 | return nil 11 | } 12 | 13 | func volumeName(name string) MountOption { 14 | return func(conf *mountConfig) error { 15 | conf.options["volname"] = name 16 | return nil 17 | } 18 | } 19 | 20 | func daemonTimeout(name string) MountOption { 21 | return func(conf *mountConfig) error { 22 | conf.options["daemon_timeout"] = name 23 | return nil 24 | } 25 | } 26 | 27 | func noAppleXattr(conf *mountConfig) error { 28 | conf.options["noapplexattr"] = "" 29 | return nil 30 | } 31 | 32 | func noAppleDouble(conf *mountConfig) error { 33 | conf.options["noappledouble"] = "" 34 | return nil 35 | } 36 | 37 | func exclCreate(conf *mountConfig) error { 38 | conf.options["excl_create"] = "" 39 | return nil 40 | } 41 | 42 | func noBrowse(conf *mountConfig) error { 43 | conf.options["nobrowse"] = "" 44 | return nil 45 | } 46 | -------------------------------------------------------------------------------- /options_freebsd.go: -------------------------------------------------------------------------------- 1 | package fuse 2 | 3 | func autoXattr(conf *mountConfig) error { 4 | return nil 5 | } 6 | 7 | func localVolume(conf *mountConfig) error { 8 | return nil 9 | } 10 | 11 | func volumeName(name string) MountOption { 12 | return dummyOption 13 | } 14 | 15 | func daemonTimeout(name string) MountOption { 16 | return dummyOption 17 | } 18 | 19 | func noAppleXattr(conf *mountConfig) error { 20 | return nil 21 | } 22 | 23 | func noAppleDouble(conf *mountConfig) error { 24 | return nil 25 | } 26 | 27 | func exclCreate(conf *mountConfig) error { 28 | return nil 29 | } 30 | 31 | func noBrowse(conf *mountConfig) error { 32 | return nil 33 | } 34 | -------------------------------------------------------------------------------- /options_helper_test.go: -------------------------------------------------------------------------------- 1 | package fuse 2 | 3 | // for TestMountOptionCommaError 4 | func ForTestSetMountOption(k, v string) MountOption { 5 | fn := func(conf *mountConfig) error { 6 | conf.options[k] = v 7 | return nil 8 | } 9 | return fn 10 | } 11 | -------------------------------------------------------------------------------- /options_linux.go: -------------------------------------------------------------------------------- 1 | package fuse 2 | 3 | func autoXattr(conf *mountConfig) error { 4 | return nil 5 | } 6 | 7 | func localVolume(conf *mountConfig) error { 8 | return nil 9 | } 10 | 11 | func volumeName(name string) MountOption { 12 | return dummyOption 13 | } 14 | 15 | func daemonTimeout(name string) MountOption { 16 | return dummyOption 17 | } 18 | 19 | func noAppleXattr(conf *mountConfig) error { 20 | return nil 21 | } 22 | 23 | func noAppleDouble(conf *mountConfig) error { 24 | return nil 25 | } 26 | 27 | func exclCreate(conf *mountConfig) error { 28 | return nil 29 | } 30 | 31 | func noBrowse(conf *mountConfig) error { 32 | return nil 33 | } 34 | -------------------------------------------------------------------------------- /options_nocomma_test.go: -------------------------------------------------------------------------------- 1 | // This file contains tests for platforms that have no escape 2 | // mechanism for including commas in mount options. 3 | // 4 | // +build darwin 5 | 6 | package fuse_test 7 | 8 | import ( 9 | "runtime" 10 | "testing" 11 | 12 | "github.com/seaweedfs/fuse" 13 | "github.com/seaweedfs/fuse/fs/fstestutil" 14 | ) 15 | 16 | func TestMountOptionCommaError(t *testing.T) { 17 | t.Parallel() 18 | // this test is not tied to any specific option, it just needs 19 | // some string content 20 | var evil = "FuseTest,Marker" 21 | mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil, 22 | fuse.ForTestSetMountOption("fusetest", evil), 23 | ) 24 | if err == nil { 25 | mnt.Close() 26 | t.Fatal("expected an error about commas") 27 | } 28 | if g, e := err.Error(), `mount options cannot contain commas on `+runtime.GOOS+`: "fusetest"="FuseTest,Marker"`; g != e { 29 | t.Fatalf("wrong error: %q != %q", g, e) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /options_test.go: -------------------------------------------------------------------------------- 1 | package fuse_test 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "runtime" 7 | "syscall" 8 | "testing" 9 | 10 | "github.com/seaweedfs/fuse" 11 | "github.com/seaweedfs/fuse/fs" 12 | "github.com/seaweedfs/fuse/fs/fstestutil" 13 | ) 14 | 15 | func init() { 16 | fstestutil.DebugByDefault() 17 | } 18 | 19 | func TestMountOptionFSName(t *testing.T) { 20 | if runtime.GOOS == "freebsd" { 21 | t.Skip("FreeBSD does not support FSName") 22 | } 23 | t.Parallel() 24 | const name = "FuseTestMarker" 25 | mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil, 26 | fuse.FSName(name), 27 | ) 28 | if err != nil { 29 | t.Fatal(err) 30 | } 31 | defer mnt.Close() 32 | 33 | info, err := fstestutil.GetMountInfo(mnt.Dir) 34 | if err != nil { 35 | t.Fatal(err) 36 | } 37 | if g, e := info.FSName, name; g != e { 38 | t.Errorf("wrong FSName: %q != %q", g, e) 39 | } 40 | } 41 | 42 | func testMountOptionFSNameEvil(t *testing.T, evil string) { 43 | if runtime.GOOS == "freebsd" { 44 | t.Skip("FreeBSD does not support FSName") 45 | } 46 | t.Parallel() 47 | var name = "FuseTest" + evil + "Marker" 48 | mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil, 49 | fuse.FSName(name), 50 | ) 51 | if err != nil { 52 | t.Fatal(err) 53 | } 54 | defer mnt.Close() 55 | 56 | info, err := fstestutil.GetMountInfo(mnt.Dir) 57 | if err != nil { 58 | t.Fatal(err) 59 | } 60 | if g, e := info.FSName, name; g != e { 61 | t.Errorf("wrong FSName: %q != %q", g, e) 62 | } 63 | } 64 | 65 | func TestMountOptionFSNameEvilComma(t *testing.T) { 66 | if runtime.GOOS == "darwin" { 67 | // see TestMountOptionCommaError for a test that enforces we 68 | // at least give a nice error, instead of corrupting the mount 69 | // options 70 | t.Skip("TODO: OS X gets this wrong, commas in mount options cannot be escaped at all") 71 | } 72 | testMountOptionFSNameEvil(t, ",") 73 | } 74 | 75 | func TestMountOptionFSNameEvilSpace(t *testing.T) { 76 | testMountOptionFSNameEvil(t, " ") 77 | } 78 | 79 | func TestMountOptionFSNameEvilTab(t *testing.T) { 80 | testMountOptionFSNameEvil(t, "\t") 81 | } 82 | 83 | func TestMountOptionFSNameEvilNewline(t *testing.T) { 84 | testMountOptionFSNameEvil(t, "\n") 85 | } 86 | 87 | func TestMountOptionFSNameEvilBackslash(t *testing.T) { 88 | testMountOptionFSNameEvil(t, `\`) 89 | } 90 | 91 | func TestMountOptionFSNameEvilBackslashDouble(t *testing.T) { 92 | // catch double-unescaping, if it were to happen 93 | testMountOptionFSNameEvil(t, `\\`) 94 | } 95 | 96 | func TestMountOptionSubtype(t *testing.T) { 97 | if runtime.GOOS == "darwin" { 98 | t.Skip("OS X does not support Subtype") 99 | } 100 | if runtime.GOOS == "freebsd" { 101 | t.Skip("FreeBSD does not support Subtype") 102 | } 103 | t.Parallel() 104 | const name = "FuseTestMarker" 105 | mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil, 106 | fuse.Subtype(name), 107 | ) 108 | if err != nil { 109 | t.Fatal(err) 110 | } 111 | defer mnt.Close() 112 | 113 | info, err := fstestutil.GetMountInfo(mnt.Dir) 114 | if err != nil { 115 | t.Fatal(err) 116 | } 117 | if g, e := info.Type, "fuse."+name; g != e { 118 | t.Errorf("wrong Subtype: %q != %q", g, e) 119 | } 120 | } 121 | 122 | // TODO test LocalVolume 123 | 124 | // TODO test AllowOther; hard because needs system-level authorization 125 | 126 | func TestMountOptionAllowOtherThenAllowRoot(t *testing.T) { 127 | t.Parallel() 128 | mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil, 129 | fuse.AllowOther(), 130 | fuse.AllowRoot(), 131 | ) 132 | if err == nil { 133 | mnt.Close() 134 | } 135 | if g, e := err, fuse.ErrCannotCombineAllowOtherAndAllowRoot; g != e { 136 | t.Fatalf("wrong error: %v != %v", g, e) 137 | } 138 | } 139 | 140 | // TODO test AllowRoot; hard because needs system-level authorization 141 | 142 | func TestMountOptionAllowRootThenAllowOther(t *testing.T) { 143 | t.Parallel() 144 | mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil, 145 | fuse.AllowRoot(), 146 | fuse.AllowOther(), 147 | ) 148 | if err == nil { 149 | mnt.Close() 150 | } 151 | if g, e := err, fuse.ErrCannotCombineAllowOtherAndAllowRoot; g != e { 152 | t.Fatalf("wrong error: %v != %v", g, e) 153 | } 154 | } 155 | 156 | type unwritableFile struct{} 157 | 158 | func (f unwritableFile) Attr(ctx context.Context, a *fuse.Attr) error { 159 | a.Mode = 0000 160 | return nil 161 | } 162 | 163 | func TestMountOptionDefaultPermissions(t *testing.T) { 164 | if runtime.GOOS == "freebsd" { 165 | t.Skip("FreeBSD does not support DefaultPermissions") 166 | } 167 | t.Parallel() 168 | 169 | mnt, err := fstestutil.MountedT(t, 170 | fstestutil.SimpleFS{ 171 | &fstestutil.ChildMap{"child": unwritableFile{}}, 172 | }, 173 | nil, 174 | fuse.DefaultPermissions(), 175 | ) 176 | if err != nil { 177 | t.Fatal(err) 178 | } 179 | defer mnt.Close() 180 | 181 | // This will be prevented by kernel-level access checking when 182 | // DefaultPermissions is used. 183 | f, err := os.OpenFile(mnt.Dir+"/child", os.O_WRONLY, 0000) 184 | if err == nil { 185 | f.Close() 186 | t.Fatal("expected an error") 187 | } 188 | if !os.IsPermission(err) { 189 | t.Fatalf("expected a permission error, got %T: %v", err, err) 190 | } 191 | } 192 | 193 | type createrDir struct { 194 | fstestutil.Dir 195 | } 196 | 197 | var _ fs.NodeCreater = createrDir{} 198 | 199 | func (createrDir) Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (fs.Node, fs.Handle, error) { 200 | // pick a really distinct error, to identify it later 201 | return nil, nil, fuse.Errno(syscall.ENAMETOOLONG) 202 | } 203 | 204 | func TestMountOptionReadOnly(t *testing.T) { 205 | t.Parallel() 206 | 207 | mnt, err := fstestutil.MountedT(t, 208 | fstestutil.SimpleFS{createrDir{}}, 209 | nil, 210 | fuse.ReadOnly(), 211 | ) 212 | if err != nil { 213 | t.Fatal(err) 214 | } 215 | defer mnt.Close() 216 | 217 | // This will be prevented by kernel-level access checking when 218 | // ReadOnly is used. 219 | f, err := os.Create(mnt.Dir + "/child") 220 | if err == nil { 221 | f.Close() 222 | t.Fatal("expected an error") 223 | } 224 | perr, ok := err.(*os.PathError) 225 | if !ok { 226 | t.Fatalf("expected PathError, got %T: %v", err, err) 227 | } 228 | if perr.Err != syscall.EROFS { 229 | t.Fatalf("expected EROFS, got %T: %v", err, err) 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /protocol.go: -------------------------------------------------------------------------------- 1 | package fuse 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // Protocol is a FUSE protocol version number. 8 | type Protocol struct { 9 | Major uint32 10 | Minor uint32 11 | } 12 | 13 | func (p Protocol) String() string { 14 | return fmt.Sprintf("%d.%d", p.Major, p.Minor) 15 | } 16 | 17 | // LT returns whether a is less than b. 18 | func (a Protocol) LT(b Protocol) bool { 19 | return a.Major < b.Major || 20 | (a.Major == b.Major && a.Minor < b.Minor) 21 | } 22 | 23 | // GE returns whether a is greater than or equal to b. 24 | func (a Protocol) GE(b Protocol) bool { 25 | return a.Major > b.Major || 26 | (a.Major == b.Major && a.Minor >= b.Minor) 27 | } 28 | 29 | func (a Protocol) is79() bool { 30 | return a.GE(Protocol{7, 9}) 31 | } 32 | 33 | // HasAttrBlockSize returns whether Attr.BlockSize is respected by the 34 | // kernel. 35 | func (a Protocol) HasAttrBlockSize() bool { 36 | return a.is79() 37 | } 38 | 39 | // HasReadWriteFlags returns whether ReadRequest/WriteRequest 40 | // fields Flags and FileFlags are valid. 41 | func (a Protocol) HasReadWriteFlags() bool { 42 | return a.is79() 43 | } 44 | 45 | // HasGetattrFlags returns whether GetattrRequest field Flags is 46 | // valid. 47 | func (a Protocol) HasGetattrFlags() bool { 48 | return a.is79() 49 | } 50 | 51 | func (a Protocol) is710() bool { 52 | return a.GE(Protocol{7, 10}) 53 | } 54 | 55 | // HasOpenNonSeekable returns whether OpenResponse field Flags flag 56 | // OpenNonSeekable is supported. 57 | func (a Protocol) HasOpenNonSeekable() bool { 58 | return a.is710() 59 | } 60 | 61 | func (a Protocol) is712() bool { 62 | return a.GE(Protocol{7, 12}) 63 | } 64 | 65 | // HasUmask returns whether CreateRequest/MkdirRequest/MknodRequest 66 | // field Umask is valid. 67 | func (a Protocol) HasUmask() bool { 68 | return a.is712() 69 | } 70 | 71 | // HasInvalidate returns whether InvalidateNode/InvalidateEntry are 72 | // supported. 73 | func (a Protocol) HasInvalidate() bool { 74 | return a.is712() 75 | } 76 | -------------------------------------------------------------------------------- /syscallx/doc.go: -------------------------------------------------------------------------------- 1 | // Package syscallx provides wrappers that make syscalls on various 2 | // platforms more interoperable. 3 | // 4 | // The API intentionally omits the OS X-specific position and option 5 | // arguments for extended attribute calls. 6 | // 7 | // Not having position means it might not be useful for accessing the 8 | // resource fork. If that's needed by code inside fuse, a function 9 | // with a different name may be added on the side. 10 | // 11 | // Options can be implemented with separate wrappers, in the style of 12 | // Linux getxattr/lgetxattr/fgetxattr. 13 | package syscallx // import "github.com/seaweedfs/fuse/syscallx" 14 | -------------------------------------------------------------------------------- /syscallx/generate: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | mksys="$(go env GOROOT)/src/pkg/syscall/mksyscall.pl" 5 | 6 | fix() { 7 | sed 's,^package syscall$,&x\nimport "syscall",' \ 8 | | gofmt -r='BytePtrFromString -> syscall.BytePtrFromString' \ 9 | | gofmt -r='Syscall6 -> syscall.Syscall6' \ 10 | | gofmt -r='Syscall -> syscall.Syscall' \ 11 | | gofmt -r='SYS_GETXATTR -> syscall.SYS_GETXATTR' \ 12 | | gofmt -r='SYS_LISTXATTR -> syscall.SYS_LISTXATTR' \ 13 | | gofmt -r='SYS_SETXATTR -> syscall.SYS_SETXATTR' \ 14 | | gofmt -r='SYS_REMOVEXATTR -> syscall.SYS_REMOVEXATTR' \ 15 | | gofmt -r='SYS_MSYNC -> syscall.SYS_MSYNC' 16 | } 17 | 18 | cd "$(dirname "$0")" 19 | 20 | $mksys xattr_darwin.go \ 21 | | fix \ 22 | >xattr_darwin_amd64.go 23 | 24 | $mksys -l32 xattr_darwin.go \ 25 | | fix \ 26 | >xattr_darwin_386.go 27 | 28 | $mksys msync.go \ 29 | | fix \ 30 | >msync_amd64.go 31 | 32 | $mksys -l32 msync.go \ 33 | | fix \ 34 | >msync_386.go 35 | -------------------------------------------------------------------------------- /syscallx/msync.go: -------------------------------------------------------------------------------- 1 | package syscallx 2 | 3 | /* This is the source file for msync_*.go, to regenerate run 4 | 5 | ./generate 6 | 7 | */ 8 | 9 | //sys Msync(b []byte, flags int) (err error) 10 | -------------------------------------------------------------------------------- /syscallx/msync_386.go: -------------------------------------------------------------------------------- 1 | // mksyscall.pl -l32 msync.go 2 | // MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT 3 | 4 | package syscallx 5 | 6 | import "syscall" 7 | 8 | import "unsafe" 9 | 10 | // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT 11 | 12 | func Msync(b []byte, flags int) (err error) { 13 | var _p0 unsafe.Pointer 14 | if len(b) > 0 { 15 | _p0 = unsafe.Pointer(&b[0]) 16 | } else { 17 | _p0 = unsafe.Pointer(&_zero) 18 | } 19 | _, _, e1 := syscall.Syscall(syscall.SYS_MSYNC, uintptr(_p0), uintptr(len(b)), uintptr(flags)) 20 | if e1 != 0 { 21 | err = e1 22 | } 23 | return 24 | } 25 | -------------------------------------------------------------------------------- /syscallx/msync_amd64.go: -------------------------------------------------------------------------------- 1 | // mksyscall.pl msync.go 2 | // MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT 3 | 4 | package syscallx 5 | 6 | import "syscall" 7 | 8 | import "unsafe" 9 | 10 | // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT 11 | 12 | func Msync(b []byte, flags int) (err error) { 13 | var _p0 unsafe.Pointer 14 | if len(b) > 0 { 15 | _p0 = unsafe.Pointer(&b[0]) 16 | } else { 17 | _p0 = unsafe.Pointer(&_zero) 18 | } 19 | _, _, e1 := syscall.Syscall(syscall.SYS_MSYNC, uintptr(_p0), uintptr(len(b)), uintptr(flags)) 20 | if e1 != 0 { 21 | err = e1 22 | } 23 | return 24 | } 25 | -------------------------------------------------------------------------------- /syscallx/syscallx.go: -------------------------------------------------------------------------------- 1 | package syscallx 2 | 3 | // make us look more like package syscall, so mksyscall.pl output works 4 | var _zero uintptr 5 | -------------------------------------------------------------------------------- /syscallx/syscallx_std.go: -------------------------------------------------------------------------------- 1 | // +build !darwin 2 | 3 | package syscallx 4 | 5 | // This file just contains wrappers for platforms that already have 6 | // the right stuff in golang.org/x/sys/unix. 7 | 8 | import ( 9 | "golang.org/x/sys/unix" 10 | ) 11 | 12 | func Getxattr(path string, attr string, dest []byte) (sz int, err error) { 13 | return unix.Getxattr(path, attr, dest) 14 | } 15 | 16 | func Listxattr(path string, dest []byte) (sz int, err error) { 17 | return unix.Listxattr(path, dest) 18 | } 19 | 20 | func Setxattr(path string, attr string, data []byte, flags int) (err error) { 21 | return unix.Setxattr(path, attr, data, flags) 22 | } 23 | 24 | func Removexattr(path string, attr string) (err error) { 25 | return unix.Removexattr(path, attr) 26 | } 27 | -------------------------------------------------------------------------------- /syscallx/xattr_darwin.go: -------------------------------------------------------------------------------- 1 | package syscallx 2 | 3 | /* This is the source file for syscallx_darwin_*.go, to regenerate run 4 | 5 | ./generate 6 | 7 | */ 8 | 9 | // cannot use dest []byte here because OS X getxattr really wants a 10 | // NULL to trigger size probing, size==0 is not enough 11 | // 12 | //sys getxattr(path string, attr string, dest *byte, size int, position uint32, options int) (sz int, err error) 13 | 14 | func Getxattr(path string, attr string, dest []byte) (sz int, err error) { 15 | var destp *byte 16 | if len(dest) > 0 { 17 | destp = &dest[0] 18 | } 19 | return getxattr(path, attr, destp, len(dest), 0, 0) 20 | } 21 | 22 | //sys listxattr(path string, dest []byte, options int) (sz int, err error) 23 | 24 | func Listxattr(path string, dest []byte) (sz int, err error) { 25 | return listxattr(path, dest, 0) 26 | } 27 | 28 | //sys setxattr(path string, attr string, data []byte, position uint32, flags int) (err error) 29 | 30 | func Setxattr(path string, attr string, data []byte, flags int) (err error) { 31 | return setxattr(path, attr, data, 0, flags) 32 | } 33 | 34 | //sys removexattr(path string, attr string, options int) (err error) 35 | 36 | func Removexattr(path string, attr string) (err error) { 37 | return removexattr(path, attr, 0) 38 | } 39 | -------------------------------------------------------------------------------- /syscallx/xattr_darwin_386.go: -------------------------------------------------------------------------------- 1 | // mksyscall.pl -l32 xattr_darwin.go 2 | // MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT 3 | 4 | package syscallx 5 | 6 | import "syscall" 7 | 8 | import "unsafe" 9 | 10 | // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT 11 | 12 | func getxattr(path string, attr string, dest *byte, size int, position uint32, options int) (sz int, err error) { 13 | var _p0 *byte 14 | _p0, err = syscall.BytePtrFromString(path) 15 | if err != nil { 16 | return 17 | } 18 | var _p1 *byte 19 | _p1, err = syscall.BytePtrFromString(attr) 20 | if err != nil { 21 | return 22 | } 23 | r0, _, e1 := syscall.Syscall6(syscall.SYS_GETXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(unsafe.Pointer(dest)), uintptr(size), uintptr(position), uintptr(options)) 24 | sz = int(r0) 25 | if e1 != 0 { 26 | err = e1 27 | } 28 | return 29 | } 30 | 31 | // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT 32 | 33 | func listxattr(path string, dest []byte, options int) (sz int, err error) { 34 | var _p0 *byte 35 | _p0, err = syscall.BytePtrFromString(path) 36 | if err != nil { 37 | return 38 | } 39 | var _p1 unsafe.Pointer 40 | if len(dest) > 0 { 41 | _p1 = unsafe.Pointer(&dest[0]) 42 | } else { 43 | _p1 = unsafe.Pointer(&_zero) 44 | } 45 | r0, _, e1 := syscall.Syscall6(syscall.SYS_LISTXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(_p1), uintptr(len(dest)), uintptr(options), 0, 0) 46 | sz = int(r0) 47 | if e1 != 0 { 48 | err = e1 49 | } 50 | return 51 | } 52 | 53 | // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT 54 | 55 | func setxattr(path string, attr string, data []byte, position uint32, flags int) (err error) { 56 | var _p0 *byte 57 | _p0, err = syscall.BytePtrFromString(path) 58 | if err != nil { 59 | return 60 | } 61 | var _p1 *byte 62 | _p1, err = syscall.BytePtrFromString(attr) 63 | if err != nil { 64 | return 65 | } 66 | var _p2 unsafe.Pointer 67 | if len(data) > 0 { 68 | _p2 = unsafe.Pointer(&data[0]) 69 | } else { 70 | _p2 = unsafe.Pointer(&_zero) 71 | } 72 | _, _, e1 := syscall.Syscall6(syscall.SYS_SETXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(_p2), uintptr(len(data)), uintptr(position), uintptr(flags)) 73 | if e1 != 0 { 74 | err = e1 75 | } 76 | return 77 | } 78 | 79 | // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT 80 | 81 | func removexattr(path string, attr string, options int) (err error) { 82 | var _p0 *byte 83 | _p0, err = syscall.BytePtrFromString(path) 84 | if err != nil { 85 | return 86 | } 87 | var _p1 *byte 88 | _p1, err = syscall.BytePtrFromString(attr) 89 | if err != nil { 90 | return 91 | } 92 | _, _, e1 := syscall.Syscall(syscall.SYS_REMOVEXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(options)) 93 | if e1 != 0 { 94 | err = e1 95 | } 96 | return 97 | } 98 | -------------------------------------------------------------------------------- /syscallx/xattr_darwin_amd64.go: -------------------------------------------------------------------------------- 1 | // mksyscall.pl xattr_darwin.go 2 | // MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT 3 | 4 | package syscallx 5 | 6 | import "syscall" 7 | 8 | import "unsafe" 9 | 10 | // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT 11 | 12 | func getxattr(path string, attr string, dest *byte, size int, position uint32, options int) (sz int, err error) { 13 | var _p0 *byte 14 | _p0, err = syscall.BytePtrFromString(path) 15 | if err != nil { 16 | return 17 | } 18 | var _p1 *byte 19 | _p1, err = syscall.BytePtrFromString(attr) 20 | if err != nil { 21 | return 22 | } 23 | r0, _, e1 := syscall.Syscall6(syscall.SYS_GETXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(unsafe.Pointer(dest)), uintptr(size), uintptr(position), uintptr(options)) 24 | sz = int(r0) 25 | if e1 != 0 { 26 | err = e1 27 | } 28 | return 29 | } 30 | 31 | // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT 32 | 33 | func listxattr(path string, dest []byte, options int) (sz int, err error) { 34 | var _p0 *byte 35 | _p0, err = syscall.BytePtrFromString(path) 36 | if err != nil { 37 | return 38 | } 39 | var _p1 unsafe.Pointer 40 | if len(dest) > 0 { 41 | _p1 = unsafe.Pointer(&dest[0]) 42 | } else { 43 | _p1 = unsafe.Pointer(&_zero) 44 | } 45 | r0, _, e1 := syscall.Syscall6(syscall.SYS_LISTXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(_p1), uintptr(len(dest)), uintptr(options), 0, 0) 46 | sz = int(r0) 47 | if e1 != 0 { 48 | err = e1 49 | } 50 | return 51 | } 52 | 53 | // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT 54 | 55 | func setxattr(path string, attr string, data []byte, position uint32, flags int) (err error) { 56 | var _p0 *byte 57 | _p0, err = syscall.BytePtrFromString(path) 58 | if err != nil { 59 | return 60 | } 61 | var _p1 *byte 62 | _p1, err = syscall.BytePtrFromString(attr) 63 | if err != nil { 64 | return 65 | } 66 | var _p2 unsafe.Pointer 67 | if len(data) > 0 { 68 | _p2 = unsafe.Pointer(&data[0]) 69 | } else { 70 | _p2 = unsafe.Pointer(&_zero) 71 | } 72 | _, _, e1 := syscall.Syscall6(syscall.SYS_SETXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(_p2), uintptr(len(data)), uintptr(position), uintptr(flags)) 73 | if e1 != 0 { 74 | err = e1 75 | } 76 | return 77 | } 78 | 79 | // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT 80 | 81 | func removexattr(path string, attr string, options int) (err error) { 82 | var _p0 *byte 83 | _p0, err = syscall.BytePtrFromString(path) 84 | if err != nil { 85 | return 86 | } 87 | var _p1 *byte 88 | _p1, err = syscall.BytePtrFromString(attr) 89 | if err != nil { 90 | return 91 | } 92 | _, _, e1 := syscall.Syscall(syscall.SYS_REMOVEXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(options)) 93 | if e1 != 0 { 94 | err = e1 95 | } 96 | return 97 | } 98 | -------------------------------------------------------------------------------- /unmount.go: -------------------------------------------------------------------------------- 1 | package fuse 2 | 3 | // Unmount tries to unmount the filesystem mounted at dir. 4 | func Unmount(dir string) error { 5 | return unmount(dir) 6 | } 7 | -------------------------------------------------------------------------------- /unmount_linux.go: -------------------------------------------------------------------------------- 1 | package fuse 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "os/exec" 7 | ) 8 | 9 | func unmount(dir string) error { 10 | cmd := exec.Command("fusermount", "-u", dir) 11 | output, err := cmd.CombinedOutput() 12 | if err != nil { 13 | if len(output) > 0 { 14 | output = bytes.TrimRight(output, "\n") 15 | msg := err.Error() + ": " + string(output) 16 | err = errors.New(msg) 17 | } 18 | return err 19 | } 20 | return nil 21 | } 22 | -------------------------------------------------------------------------------- /unmount_std.go: -------------------------------------------------------------------------------- 1 | // +build !linux 2 | 3 | package fuse 4 | 5 | import ( 6 | "os" 7 | "syscall" 8 | ) 9 | 10 | func unmount(dir string) error { 11 | err := syscall.Unmount(dir, 0) 12 | if err != nil { 13 | err = &os.PathError{Op: "unmount", Path: dir, Err: err} 14 | return err 15 | } 16 | return nil 17 | } 18 | --------------------------------------------------------------------------------