1
0
mirror of https://github.com/ipfs/kubo.git synced 2025-06-29 01:12:24 +08:00

Merge pull request #487 from jbenet/mount-fix

mount fixes
This commit is contained in:
Juan Batiz-Benet
2015-01-04 01:28:27 -08:00
32 changed files with 1086 additions and 497 deletions

2
Godeps/Godeps.json generated
View File

@ -7,7 +7,7 @@
"Deps": [
{
"ImportPath": "bazil.org/fuse",
"Rev": "a04507d54fc3610d38ee951402d8c4acab56c7b1"
"Rev": "d62a1291477b51b24becf4def173bd843138c4b6"
},
{
"ImportPath": "bitbucket.org/kardianos/osext",

View File

@ -11,4 +11,11 @@ func stack() string {
func nop(msg interface{}) {}
// Debug is called to output debug messages, including protocol
// traces. The default behavior is to do nothing.
//
// The messages have human-friendly string representations and are
// safe to marshal to JSON.
//
// Implementations must not retain msg.
var Debug func(msg interface{}) = nop

View File

@ -0,0 +1 @@
package fstestutil

View File

@ -55,12 +55,12 @@ func (mnt *Mount) Close() {
// workaround).
//
// After successful return, caller must clean up by calling Close.
func Mounted(srv *fs.Server) (*Mount, error) {
func Mounted(srv *fs.Server, options ...fuse.MountOption) (*Mount, error) {
dir, err := ioutil.TempDir("", "fusetest")
if err != nil {
return nil, err
}
c, err := fuse.Mount(dir)
c, err := fuse.Mount(dir, options...)
if err != nil {
return nil, err
}
@ -100,7 +100,7 @@ func Mounted(srv *fs.Server) (*Mount, error) {
//
// The debug log is not enabled by default. Use `-fuse.debug` or call
// DebugByDefault to enable.
func MountedT(t testing.TB, filesys fs.FS) (*Mount, error) {
func MountedT(t testing.TB, filesys fs.FS, options ...fuse.MountOption) (*Mount, error) {
srv := &fs.Server{
FS: filesys,
}
@ -109,5 +109,5 @@ func MountedT(t testing.TB, filesys fs.FS) (*Mount, error) {
t.Logf("FUSE: %s", msg)
}
}
return Mounted(srv)
return Mounted(srv, options...)
}

View File

@ -0,0 +1,14 @@
package fstestutil
// MountInfo describes a mounted file system.
type MountInfo struct {
FSName string
Type string
}
// GetMountInfo finds information about the mount at mnt. It is
// intended for use by tests only, and only fetches information
// relevant to the current tests.
func GetMountInfo(mnt string) (*MountInfo, error) {
return getMountInfo(mnt)
}

View File

@ -0,0 +1,41 @@
package fstestutil
import (
"regexp"
"syscall"
)
// cstr converts a nil-terminated C string into a Go string
func cstr(ca []int8) string {
s := make([]byte, 0, len(ca))
for _, c := range ca {
if c == 0x00 {
break
}
s = append(s, byte(c))
}
return string(s)
}
var re = regexp.MustCompile(`\\(.)`)
// unescape removes backslash-escaping. The escaped characters are not
// mapped in any way; that is, unescape(`\n` ) == `n`.
func unescape(s string) string {
return re.ReplaceAllString(s, `$1`)
}
func getMountInfo(mnt string) (*MountInfo, error) {
var st syscall.Statfs_t
err := syscall.Statfs(mnt, &st)
if err != nil {
return nil, err
}
i := &MountInfo{
// osx getmntent(3) fails to un-escape the data, so we do it..
// this might lead to double-unescaping in the future. fun.
// TestMountOptionFSNameEvilBackslashDouble checks for that.
FSName: unescape(cstr(st.Mntfromname[:])),
}
return i, nil
}

View File

@ -0,0 +1,51 @@
package fstestutil
import (
"errors"
"io/ioutil"
"strings"
)
// Linux /proc/mounts shows current mounts.
// Same format as /etc/fstab. Quoting getmntent(3):
//
// Since fields in the mtab and fstab files are separated by whitespace,
// octal escapes are used to represent the four characters space (\040),
// tab (\011), newline (\012) and backslash (\134) in those files when
// they occur in one of the four strings in a mntent structure.
//
// http://linux.die.net/man/3/getmntent
var fstabUnescape = strings.NewReplacer(
`\040`, "\040",
`\011`, "\011",
`\012`, "\012",
`\134`, "\134",
)
var errNotFound = errors.New("mount not found")
func getMountInfo(mnt string) (*MountInfo, error) {
data, err := ioutil.ReadFile("/proc/mounts")
if err != nil {
return nil, err
}
for _, line := range strings.Split(string(data), "\n") {
fields := strings.Fields(line)
if len(fields) < 3 {
continue
}
// Fields are: fsname dir type opts freq passno
fsname := fstabUnescape.Replace(fields[0])
dir := fstabUnescape.Replace(fields[1])
fstype := fstabUnescape.Replace(fields[2])
if mnt == dir {
info := &MountInfo{
FSName: fsname,
Type: fstype,
}
return info, nil
}
}
return nil, errNotFound
}

View File

@ -0,0 +1,29 @@
package fstestutil
import (
"os"
"github.com/jbenet/go-ipfs/Godeps/_workspace/src/bazil.org/fuse"
"github.com/jbenet/go-ipfs/Godeps/_workspace/src/bazil.org/fuse/fs"
)
// SimpleFS is a trivial FS that just implements the Root method.
type SimpleFS struct {
Node fs.Node
}
var _ = fs.FS(SimpleFS{})
func (f SimpleFS) Root() (fs.Node, fuse.Error) {
return f.Node, nil
}
// File can be embedded in a struct to make it look like a file.
type File struct{}
func (f File) Attr() fuse.Attr { return fuse.Attr{Mode: 0666} }
// Dir can be embedded in a struct to make it look like a directory.
type Dir struct{}
func (f Dir) Attr() fuse.Attr { return fuse.Attr{Mode: os.ModeDir | 0777} }

View File

@ -10,7 +10,6 @@ import (
"reflect"
"strings"
"sync"
"syscall"
"time"
)
@ -295,6 +294,8 @@ type Server struct {
// Function to send debug log messages to. If nil, use fuse.Debug.
// Note that changing this or fuse.Debug may not affect existing
// calls to Serve.
//
// See fuse.Debug for the rules that log functions must follow.
Debug func(msg interface{})
}
@ -317,7 +318,7 @@ func (s *Server) Serve(c *fuse.Conn) error {
root, err := sc.fs.Root()
if err != nil {
return fmt.Errorf("cannot obtain root node: %v", syscall.Errno(err.(fuse.Errno)).Error())
return fmt.Errorf("cannot obtain root node: %v", err)
}
sc.node = append(sc.node, nil, &serveNode{inode: 1, node: root, refs: 1})
sc.handle = append(sc.handle, nil)

View File

@ -87,27 +87,6 @@ func (f childMapFS) Lookup(name string, intr fs.Intr) (fs.Node, fuse.Error) {
return child, nil
}
// simpleFS is a trivial FS that just implements the Root method.
type simpleFS struct {
node fs.Node
}
var _ = fs.FS(simpleFS{})
func (f simpleFS) Root() (fs.Node, fuse.Error) {
return f.node, nil
}
// file can be embedded in a struct to make it look like a file.
type file struct{}
func (f file) Attr() fuse.Attr { return fuse.Attr{Mode: 0666} }
// dir can be embedded in a struct to make it look like a directory.
type dir struct{}
func (f dir) Attr() fuse.Attr { return fuse.Attr{Mode: os.ModeDir | 0777} }
// symlink can be embedded in a struct to make it look like a symlink.
type symlink struct {
target string
@ -261,7 +240,9 @@ func TestStatRoot(t *testing.T) {
// Test Read calling ReadAll.
type readAll struct{ file }
type readAll struct {
fstestutil.File
}
const hi = "hello, world"
@ -299,7 +280,9 @@ func TestReadAll(t *testing.T) {
// Test Read.
type readWithHandleRead struct{ file }
type readWithHandleRead struct {
fstestutil.File
}
func (readWithHandleRead) Attr() fuse.Attr {
return fuse.Attr{
@ -327,7 +310,7 @@ func TestReadAllWithHandleRead(t *testing.T) {
// Test Release.
type release struct {
file
fstestutil.File
record.ReleaseWaiter
}
@ -353,7 +336,7 @@ func TestRelease(t *testing.T) {
// Test Write calling basic Write, with an fsync thrown in too.
type write struct {
file
fstestutil.File
record.Writes
record.Fsyncs
}
@ -401,7 +384,7 @@ func TestWrite(t *testing.T) {
// Test Write of a larger buffer.
type writeLarge struct {
file
fstestutil.File
record.Writes
}
@ -446,7 +429,7 @@ func TestWriteLarge(t *testing.T) {
// Test Write calling Setattr+Write+Flush.
type writeTruncateFlush struct {
file
fstestutil.File
record.Writes
record.Setattrs
record.Flushes
@ -479,7 +462,7 @@ func TestWriteTruncateFlush(t *testing.T) {
// Test Mkdir.
type mkdir1 struct {
dir
fstestutil.Dir
record.Mkdirs
}
@ -491,7 +474,7 @@ func (f *mkdir1) Mkdir(req *fuse.MkdirRequest, intr fs.Intr) (fs.Node, fuse.Erro
func TestMkdir(t *testing.T) {
t.Parallel()
f := &mkdir1{}
mnt, err := fstestutil.MountedT(t, simpleFS{f})
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f})
if err != nil {
t.Fatal(err)
}
@ -513,12 +496,12 @@ func TestMkdir(t *testing.T) {
// Test Create (and fsync)
type create1file struct {
file
fstestutil.File
record.Fsyncs
}
type create1 struct {
dir
fstestutil.Dir
f create1file
}
@ -553,7 +536,7 @@ func (f *create1) Create(req *fuse.CreateRequest, resp *fuse.CreateResponse, int
func TestCreate(t *testing.T) {
t.Parallel()
f := &create1{}
mnt, err := fstestutil.MountedT(t, simpleFS{f})
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f})
if err != nil {
t.Fatal(err)
}
@ -583,12 +566,12 @@ func TestCreate(t *testing.T) {
// Test Create + Write + Remove
type create3file struct {
file
fstestutil.File
record.Writes
}
type create3 struct {
dir
fstestutil.Dir
f create3file
fooCreated record.MarkRecorder
fooRemoved record.MarkRecorder
@ -622,7 +605,7 @@ func (f *create3) Remove(r *fuse.RemoveRequest, intr fs.Intr) fuse.Error {
func TestCreateWriteRemove(t *testing.T) {
t.Parallel()
f := &create3{}
mnt, err := fstestutil.MountedT(t, simpleFS{f})
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f})
if err != nil {
t.Fatal(err)
}
@ -659,7 +642,7 @@ func (f symlink1link) Readlink(*fuse.ReadlinkRequest, fs.Intr) (string, fuse.Err
}
type symlink1 struct {
dir
fstestutil.Dir
record.Symlinks
}
@ -671,7 +654,7 @@ func (f *symlink1) Symlink(req *fuse.SymlinkRequest, intr fs.Intr) (fs.Node, fus
func TestSymlink(t *testing.T) {
t.Parallel()
f := &symlink1{}
mnt, err := fstestutil.MountedT(t, simpleFS{f})
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f})
if err != nil {
t.Fatal(err)
}
@ -701,26 +684,26 @@ func TestSymlink(t *testing.T) {
// Test link
type link1 struct {
dir
fstestutil.Dir
record.Links
}
func (f *link1) Lookup(name string, intr fs.Intr) (fs.Node, fuse.Error) {
if name == "old" {
return file{}, nil
return fstestutil.File{}, nil
}
return nil, fuse.ENOENT
}
func (f *link1) Link(r *fuse.LinkRequest, old fs.Node, intr fs.Intr) (fs.Node, fuse.Error) {
f.Links.Link(r, old, intr)
return file{}, nil
return fstestutil.File{}, nil
}
func TestLink(t *testing.T) {
t.Parallel()
f := &link1{}
mnt, err := fstestutil.MountedT(t, simpleFS{f})
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f})
if err != nil {
t.Fatal(err)
}
@ -745,13 +728,13 @@ func TestLink(t *testing.T) {
// Test Rename
type rename1 struct {
dir
fstestutil.Dir
renamed record.Counter
}
func (f *rename1) Lookup(name string, intr fs.Intr) (fs.Node, fuse.Error) {
if name == "old" {
return file{}, nil
return fstestutil.File{}, nil
}
return nil, fuse.ENOENT
}
@ -767,7 +750,7 @@ func (f *rename1) Rename(r *fuse.RenameRequest, newDir fs.Node, intr fs.Intr) fu
func TestRename(t *testing.T) {
t.Parallel()
f := &rename1{}
mnt, err := fstestutil.MountedT(t, simpleFS{f})
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f})
if err != nil {
t.Fatal(err)
}
@ -789,7 +772,7 @@ func TestRename(t *testing.T) {
// Test mknod
type mknod1 struct {
dir
fstestutil.Dir
record.Mknods
}
@ -805,7 +788,7 @@ func TestMknod(t *testing.T) {
}
f := &mknod1{}
mnt, err := fstestutil.MountedT(t, simpleFS{f})
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f})
if err != nil {
t.Fatal(err)
}
@ -836,7 +819,7 @@ func TestMknod(t *testing.T) {
// Test Read served with DataHandle.
type dataHandleTest struct {
file
fstestutil.File
}
func (dataHandleTest) Attr() fuse.Attr {
@ -872,7 +855,7 @@ func TestDataHandle(t *testing.T) {
// Test interrupt
type interrupt struct {
file
fstestutil.File
// strobes to signal we have a read hanging
hanging chan struct{}
@ -955,7 +938,7 @@ func TestInterrupt(t *testing.T) {
// Test truncate
type truncate struct {
file
fstestutil.File
record.Setattrs
}
@ -996,7 +979,7 @@ func TestTruncate0(t *testing.T) {
// Test ftruncate
type ftruncate struct {
file
fstestutil.File
record.Setattrs
}
@ -1046,7 +1029,7 @@ func TestFtruncate0(t *testing.T) {
// Test opening existing file truncates
type truncateWithOpen struct {
file
fstestutil.File
record.Setattrs
}
@ -1083,7 +1066,7 @@ func TestTruncateWithOpen(t *testing.T) {
// Test readdir
type readdir struct {
dir
fstestutil.Dir
}
func (d *readdir) ReadDir(intr fs.Intr) ([]fuse.Dirent, fuse.Error) {
@ -1097,7 +1080,7 @@ func (d *readdir) ReadDir(intr fs.Intr) ([]fuse.Dirent, fuse.Error) {
func TestReadDir(t *testing.T) {
t.Parallel()
f := &readdir{}
mnt, err := fstestutil.MountedT(t, simpleFS{f})
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f})
if err != nil {
t.Fatal(err)
}
@ -1133,7 +1116,7 @@ func TestReadDir(t *testing.T) {
// Test Chmod.
type chmod struct {
file
fstestutil.File
record.Setattrs
}
@ -1169,7 +1152,7 @@ func TestChmod(t *testing.T) {
// Test open
type open struct {
file
fstestutil.File
record.Opens
}
@ -1233,14 +1216,14 @@ func TestOpen(t *testing.T) {
// Test Fsync on a dir
type fsyncDir struct {
dir
fstestutil.Dir
record.Fsyncs
}
func TestFsyncDir(t *testing.T) {
t.Parallel()
f := &fsyncDir{}
mnt, err := fstestutil.MountedT(t, simpleFS{f})
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f})
if err != nil {
t.Fatal(err)
}
@ -1278,7 +1261,7 @@ func TestFsyncDir(t *testing.T) {
// Test Getxattr
type getxattr struct {
file
fstestutil.File
record.Getxattrs
}
@ -1316,7 +1299,7 @@ func TestGetxattr(t *testing.T) {
// Test Getxattr that has no space to return value
type getxattrTooSmall struct {
file
fstestutil.File
}
func (f *getxattrTooSmall) Getxattr(req *fuse.GetxattrRequest, resp *fuse.GetxattrResponse, intr fs.Intr) fuse.Error {
@ -1347,7 +1330,7 @@ func TestGetxattrTooSmall(t *testing.T) {
// Test Getxattr used to probe result size
type getxattrSize struct {
file
fstestutil.File
}
func (f *getxattrSize) Getxattr(req *fuse.GetxattrRequest, resp *fuse.GetxattrResponse, intr fs.Intr) fuse.Error {
@ -1377,7 +1360,7 @@ func TestGetxattrSize(t *testing.T) {
// Test Listxattr
type listxattr struct {
file
fstestutil.File
record.Listxattrs
}
@ -1418,7 +1401,7 @@ func TestListxattr(t *testing.T) {
// Test Listxattr that has no space to return value
type listxattrTooSmall struct {
file
fstestutil.File
}
func (f *listxattrTooSmall) Listxattr(req *fuse.ListxattrRequest, resp *fuse.ListxattrResponse, intr fs.Intr) fuse.Error {
@ -1449,7 +1432,7 @@ func TestListxattrTooSmall(t *testing.T) {
// Test Listxattr used to probe result size
type listxattrSize struct {
file
fstestutil.File
}
func (f *listxattrSize) Listxattr(req *fuse.ListxattrRequest, resp *fuse.ListxattrResponse, intr fs.Intr) fuse.Error {
@ -1479,11 +1462,16 @@ func TestListxattrSize(t *testing.T) {
// Test Setxattr
type setxattr struct {
file
fstestutil.File
record.Setxattrs
}
func TestSetxattr(t *testing.T) {
func testSetxattr(t *testing.T, size int) {
const linux_XATTR_NAME_MAX = 64 * 1024
if size > linux_XATTR_NAME_MAX && runtime.GOOS == "linux" {
t.Skip("large xattrs are not supported by linux")
}
t.Parallel()
f := &setxattr{}
mnt, err := fstestutil.MountedT(t, childMapFS{"child": f})
@ -1492,7 +1480,9 @@ func TestSetxattr(t *testing.T) {
}
defer mnt.Close()
err = syscallx.Setxattr(mnt.Dir+"/child", "greeting", []byte("hello, world"), 0)
const g = "hello, world"
greeting := strings.Repeat(g, size/len(g)+1)[:size]
err = syscallx.Setxattr(mnt.Dir+"/child", "greeting", []byte(greeting), 0)
if err != nil {
t.Errorf("unexpected error: %v", err)
return
@ -1510,15 +1500,27 @@ func TestSetxattr(t *testing.T) {
t.Errorf("Setxattr incorrect flags: %d != %d", g, e)
}
if g, e := string(got.Xattr), "hello, world"; g != e {
if g, e := string(got.Xattr), greeting; g != e {
t.Errorf("Setxattr incorrect data: %q != %q", g, e)
}
}
func TestSetxattr(t *testing.T) {
testSetxattr(t, 20)
}
func TestSetxattr64kB(t *testing.T) {
testSetxattr(t, 64*1024)
}
func TestSetxattr16MB(t *testing.T) {
testSetxattr(t, 16*1024*1024)
}
// Test Removexattr
type removexattr struct {
file
fstestutil.File
record.Removexattrs
}
@ -1546,7 +1548,7 @@ func TestRemovexattr(t *testing.T) {
// Test default error.
type defaultErrno struct {
dir
fstestutil.Dir
}
func (f defaultErrno) Lookup(name string, intr fs.Intr) (fs.Node, fuse.Error) {
@ -1555,7 +1557,7 @@ func (f defaultErrno) Lookup(name string, intr fs.Intr) (fs.Node, fuse.Error) {
func TestDefaultErrno(t *testing.T) {
t.Parallel()
mnt, err := fstestutil.MountedT(t, simpleFS{defaultErrno{}})
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{defaultErrno{}})
if err != nil {
t.Fatal(err)
}
@ -1580,7 +1582,7 @@ func TestDefaultErrno(t *testing.T) {
// Test custom error.
type customErrNode struct {
dir
fstestutil.Dir
}
type myCustomError struct {
@ -1601,7 +1603,7 @@ func (f customErrNode) Lookup(name string, intr fs.Intr) (fs.Node, fuse.Error) {
func TestCustomErrno(t *testing.T) {
t.Parallel()
mnt, err := fstestutil.MountedT(t, simpleFS{customErrNode{}})
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{customErrNode{}})
if err != nil {
t.Fatal(err)
}
@ -1736,7 +1738,9 @@ func TestMmap(t *testing.T) {
// Test direct Read.
type directRead struct{ file }
type directRead struct {
fstestutil.File
}
// explicitly not defining Attr and setting Size

View File

@ -74,7 +74,8 @@
//
// Mount Options
//
// XXX
// Behavior and metadata of the mounted file system can be changed by
// passing MountOption values to Mount.
//
package fuse
@ -120,13 +121,21 @@ type Conn struct {
// visible until after Conn.Ready is closed. See Conn.MountError for
// possible errors. Incoming requests on Conn must be served to make
// progress.
func Mount(dir string) (*Conn, error) {
// TODO(rsc): mount options (...string?)
func Mount(dir string, options ...MountOption) (*Conn, error) {
conf := MountConfig{
options: make(map[string]string),
}
for _, option := range options {
if err := option(&conf); err != nil {
return nil, err
}
}
ready := make(chan struct{}, 1)
c := &Conn{
Ready: ready,
}
f, err := mount(dir, ready, &c.MountError)
f, err := mount(dir, &conf, ready, &c.MountError)
if err != nil {
return nil, err
}
@ -170,6 +179,9 @@ type Header struct {
Uid uint32 // user ID of process making request
Gid uint32 // group ID of process making request
Pid uint32 // process ID of process making request
// for returning to reqPool
msg *message
}
func (h *Header) String() string {
@ -180,6 +192,20 @@ func (h *Header) Hdr() *Header {
return h
}
func (h *Header) noResponse() {
putMessage(h.msg)
}
func (h *Header) respond(out *outHeader, n uintptr) {
h.Conn.respond(out, n)
putMessage(h.msg)
}
func (h *Header) respondData(out *outHeader, n uintptr, data []byte) {
h.Conn.respondData(out, n, data)
putMessage(h.msg)
}
// An Error is a FUSE error.
//
// Errors messages will be visible in the debug log as part of the
@ -279,17 +305,48 @@ func (h *Header) RespondError(err Error) {
// FUSE uses negative errors!
// TODO: File bug report against OSXFUSE: positive error causes kernel panic.
out := &outHeader{Error: -int32(errno), Unique: uint64(h.ID)}
h.Conn.respond(out, unsafe.Sizeof(*out))
h.respond(out, unsafe.Sizeof(*out))
}
// Maximum file write size we are prepared to receive from the kernel.
const maxWrite = 128 * 1024
const maxWrite = 16 * 1024 * 1024
// All requests read from the kernel, without data, are shorter than
// this.
var maxRequestSize = syscall.Getpagesize()
var bufSize = maxRequestSize + maxWrite
// reqPool is a pool of messages.
//
// Lifetime of a logical message is from getMessage to putMessage.
// getMessage is called by ReadRequest. putMessage is called by
// Conn.ReadRequest, Request.Respond, or Request.RespondError.
//
// Messages in the pool are guaranteed to have conn and off zeroed,
// buf allocated and len==bufSize, and hdr set.
var reqPool = sync.Pool{
New: allocMessage,
}
func allocMessage() interface{} {
m := &message{buf: make([]byte, bufSize)}
m.hdr = (*inHeader)(unsafe.Pointer(&m.buf[0]))
return m
}
func getMessage(c *Conn) *message {
m := reqPool.Get().(*message)
m.conn = c
return m
}
func putMessage(m *message) {
m.buf = m.buf[:bufSize]
m.conn = nil
m.off = 0
reqPool.Put(m)
}
// a message represents the bytes of a single FUSE message
type message struct {
conn *Conn
@ -298,12 +355,6 @@ type message struct {
off int // offset for reading additional fields
}
func newMessage(c *Conn) *message {
m := &message{conn: c, buf: make([]byte, bufSize)}
m.hdr = (*inHeader)(unsafe.Pointer(&m.buf[0]))
return m
}
func (m *message) len() uintptr {
return uintptr(len(m.buf) - m.off)
}
@ -322,7 +373,16 @@ func (m *message) bytes() []byte {
func (m *message) Header() Header {
h := m.hdr
return Header{Conn: m.conn, ID: RequestID(h.Unique), Node: NodeID(h.Nodeid), Uid: h.Uid, Gid: h.Gid, Pid: h.Pid}
return Header{
Conn: m.conn,
ID: RequestID(h.Unique),
Node: NodeID(h.Nodeid),
Uid: h.Uid,
Gid: h.Gid,
Pid: h.Pid,
msg: m,
}
}
// fileMode returns a Go os.FileMode from a Unix mode.
@ -385,9 +445,12 @@ func (c *Conn) fd() int {
return int(c.dev.Fd())
}
// ReadRequest returns the next FUSE request from the kernel.
//
// Caller must call either Request.Respond or Request.RespondError in
// a reasonable time. Caller must not retain Request after that call.
func (c *Conn) ReadRequest() (Request, error) {
// TODO: Some kind of buffer reuse.
m := newMessage(c)
m := getMessage(c)
loop:
c.rio.RLock()
n, err := syscall.Read(c.fd(), m.buf)
@ -398,14 +461,17 @@ loop:
goto loop
}
if err != nil && err != syscall.ENODEV {
putMessage(m)
return nil, err
}
if n <= 0 {
putMessage(m)
return nil, io.EOF
}
m.buf = m.buf[:n]
if n < inHeaderSize {
putMessage(m)
return nil, errors.New("fuse: message too short")
}
@ -421,7 +487,10 @@ loop:
}
if m.hdr.Len != uint32(n) {
return nil, fmt.Errorf("fuse: read %d opcode %d but expected %d", n, m.hdr.Opcode, m.hdr.Len)
// prepare error message before returning m to pool
err := fmt.Errorf("fuse: read %d opcode %d but expected %d", n, m.hdr.Opcode, m.hdr.Len)
putMessage(m)
return nil, err
}
m.off = inHeaderSize
@ -821,6 +890,7 @@ loop:
corrupt:
Debug(malformedMessage{})
putMessage(m)
return nil, fmt.Errorf("fuse: malformed message")
unrecognized:
@ -886,6 +956,8 @@ type InitRequest struct {
Flags InitFlags
}
var _ = Request(&InitRequest{})
func (r *InitRequest) String() string {
return fmt.Sprintf("Init [%s] %d.%d ra=%d fl=%v", &r.Header, r.Major, r.Minor, r.MaxReadahead, r.Flags)
}
@ -920,7 +992,7 @@ func (r *InitRequest) Respond(resp *InitResponse) {
if out.MaxWrite > maxWrite {
out.MaxWrite = maxWrite
}
r.Conn.respond(&out.outHeader, unsafe.Sizeof(*out))
r.respond(&out.outHeader, unsafe.Sizeof(*out))
}
// A StatfsRequest requests information about the mounted file system.
@ -928,8 +1000,10 @@ type StatfsRequest struct {
Header `json:"-"`
}
var _ = Request(&StatfsRequest{})
func (r *StatfsRequest) String() string {
return fmt.Sprintf("Statfs [%s]\n", &r.Header)
return fmt.Sprintf("Statfs [%s]", &r.Header)
}
// Respond replies to the request with the given response.
@ -946,7 +1020,7 @@ func (r *StatfsRequest) Respond(resp *StatfsResponse) {
Frsize: resp.Frsize,
},
}
r.Conn.respond(&out.outHeader, unsafe.Sizeof(*out))
r.respond(&out.outHeader, unsafe.Sizeof(*out))
}
// A StatfsResponse is the response to a StatfsRequest.
@ -972,6 +1046,8 @@ type AccessRequest struct {
Mask uint32
}
var _ = Request(&AccessRequest{})
func (r *AccessRequest) String() string {
return fmt.Sprintf("Access [%s] mask=%#x", &r.Header, r.Mask)
}
@ -980,7 +1056,7 @@ func (r *AccessRequest) String() string {
// To deny access, use RespondError.
func (r *AccessRequest) Respond() {
out := &outHeader{Unique: uint64(r.ID)}
r.Conn.respond(out, unsafe.Sizeof(*out))
r.respond(out, unsafe.Sizeof(*out))
}
// An Attr is the metadata for a single file or directory.
@ -1057,6 +1133,8 @@ type GetattrRequest struct {
Header `json:"-"`
}
var _ = Request(&GetattrRequest{})
func (r *GetattrRequest) String() string {
return fmt.Sprintf("Getattr [%s]", &r.Header)
}
@ -1069,7 +1147,7 @@ func (r *GetattrRequest) Respond(resp *GetattrResponse) {
AttrValidNsec: uint32(resp.AttrValid % time.Second / time.Nanosecond),
Attr: resp.Attr.attr(),
}
r.Conn.respond(&out.outHeader, unsafe.Sizeof(*out))
r.respond(&out.outHeader, unsafe.Sizeof(*out))
}
// A GetattrResponse is the response to a GetattrRequest.
@ -1099,6 +1177,8 @@ type GetxattrRequest struct {
Position uint32
}
var _ = Request(&GetxattrRequest{})
func (r *GetxattrRequest) String() string {
return fmt.Sprintf("Getxattr [%s] %q %d @%d", &r.Header, r.Name, r.Size, r.Position)
}
@ -1110,10 +1190,10 @@ func (r *GetxattrRequest) Respond(resp *GetxattrResponse) {
outHeader: outHeader{Unique: uint64(r.ID)},
Size: uint32(len(resp.Xattr)),
}
r.Conn.respond(&out.outHeader, unsafe.Sizeof(*out))
r.respond(&out.outHeader, unsafe.Sizeof(*out))
} else {
out := &outHeader{Unique: uint64(r.ID)}
r.Conn.respondData(out, unsafe.Sizeof(*out), resp.Xattr)
r.respondData(out, unsafe.Sizeof(*out), resp.Xattr)
}
}
@ -1138,6 +1218,8 @@ type ListxattrRequest struct {
Position uint32 // offset within attribute list
}
var _ = Request(&ListxattrRequest{})
func (r *ListxattrRequest) String() string {
return fmt.Sprintf("Listxattr [%s] %d @%d", &r.Header, r.Size, r.Position)
}
@ -1149,10 +1231,10 @@ func (r *ListxattrRequest) Respond(resp *ListxattrResponse) {
outHeader: outHeader{Unique: uint64(r.ID)},
Size: uint32(len(resp.Xattr)),
}
r.Conn.respond(&out.outHeader, unsafe.Sizeof(*out))
r.respond(&out.outHeader, unsafe.Sizeof(*out))
} else {
out := &outHeader{Unique: uint64(r.ID)}
r.Conn.respondData(out, unsafe.Sizeof(*out), resp.Xattr)
r.respondData(out, unsafe.Sizeof(*out), resp.Xattr)
}
}
@ -1179,6 +1261,8 @@ type RemovexattrRequest struct {
Name string // name of extended attribute
}
var _ = Request(&RemovexattrRequest{})
func (r *RemovexattrRequest) String() string {
return fmt.Sprintf("Removexattr [%s] %q", &r.Header, r.Name)
}
@ -1186,7 +1270,7 @@ func (r *RemovexattrRequest) String() string {
// Respond replies to the request, indicating that the attribute was removed.
func (r *RemovexattrRequest) Respond() {
out := &outHeader{Unique: uint64(r.ID)}
r.Conn.respond(out, unsafe.Sizeof(*out))
r.respond(out, unsafe.Sizeof(*out))
}
func (r *RemovexattrRequest) RespondError(err Error) {
@ -1219,14 +1303,24 @@ type SetxattrRequest struct {
Xattr []byte
}
var _ = Request(&SetxattrRequest{})
func trunc(b []byte, max int) ([]byte, string) {
if len(b) > max {
return b[:max], "..."
}
return b, ""
}
func (r *SetxattrRequest) String() string {
return fmt.Sprintf("Setxattr [%s] %q %x fl=%v @%#x", &r.Header, r.Name, r.Xattr, r.Flags, r.Position)
xattr, tail := trunc(r.Xattr, 16)
return fmt.Sprintf("Setxattr [%s] %q %x%s fl=%v @%#x", &r.Header, r.Name, xattr, tail, r.Flags, r.Position)
}
// Respond replies to the request, indicating that the extended attribute was set.
func (r *SetxattrRequest) Respond() {
out := &outHeader{Unique: uint64(r.ID)}
r.Conn.respond(out, unsafe.Sizeof(*out))
r.respond(out, unsafe.Sizeof(*out))
}
func (r *SetxattrRequest) RespondError(err Error) {
@ -1240,6 +1334,8 @@ type LookupRequest struct {
Name string
}
var _ = Request(&LookupRequest{})
func (r *LookupRequest) String() string {
return fmt.Sprintf("Lookup [%s] %q", &r.Header, r.Name)
}
@ -1256,7 +1352,7 @@ func (r *LookupRequest) Respond(resp *LookupResponse) {
AttrValidNsec: uint32(resp.AttrValid % time.Second / time.Nanosecond),
Attr: resp.Attr.attr(),
}
r.Conn.respond(&out.outHeader, unsafe.Sizeof(*out))
r.respond(&out.outHeader, unsafe.Sizeof(*out))
}
// A LookupResponse is the response to a LookupRequest.
@ -1279,6 +1375,8 @@ type OpenRequest struct {
Flags OpenFlags
}
var _ = Request(&OpenRequest{})
func (r *OpenRequest) String() string {
return fmt.Sprintf("Open [%s] dir=%v fl=%v", &r.Header, r.Dir, r.Flags)
}
@ -1290,7 +1388,7 @@ func (r *OpenRequest) Respond(resp *OpenResponse) {
Fh: uint64(resp.Handle),
OpenFlags: uint32(resp.Flags),
}
r.Conn.respond(&out.outHeader, unsafe.Sizeof(*out))
r.respond(&out.outHeader, unsafe.Sizeof(*out))
}
// A OpenResponse is the response to a OpenRequest.
@ -1311,6 +1409,8 @@ type CreateRequest struct {
Mode os.FileMode
}
var _ = Request(&CreateRequest{})
func (r *CreateRequest) String() string {
return fmt.Sprintf("Create [%s] %q fl=%v mode=%v", &r.Header, r.Name, r.Flags, r.Mode)
}
@ -1331,7 +1431,7 @@ func (r *CreateRequest) Respond(resp *CreateResponse) {
Fh: uint64(resp.Handle),
OpenFlags: uint32(resp.Flags),
}
r.Conn.respond(&out.outHeader, unsafe.Sizeof(*out))
r.respond(&out.outHeader, unsafe.Sizeof(*out))
}
// A CreateResponse is the response to a CreateRequest.
@ -1352,6 +1452,8 @@ type MkdirRequest struct {
Mode os.FileMode
}
var _ = Request(&MkdirRequest{})
func (r *MkdirRequest) String() string {
return fmt.Sprintf("Mkdir [%s] %q mode=%v", &r.Header, r.Name, r.Mode)
}
@ -1368,7 +1470,7 @@ func (r *MkdirRequest) Respond(resp *MkdirResponse) {
AttrValidNsec: uint32(resp.AttrValid % time.Second / time.Nanosecond),
Attr: resp.Attr.attr(),
}
r.Conn.respond(&out.outHeader, unsafe.Sizeof(*out))
r.respond(&out.outHeader, unsafe.Sizeof(*out))
}
// A MkdirResponse is the response to a MkdirRequest.
@ -1389,6 +1491,8 @@ type ReadRequest struct {
Size int
}
var _ = Request(&ReadRequest{})
func (r *ReadRequest) String() string {
return fmt.Sprintf("Read [%s] %#x %d @%#x dir=%v", &r.Header, r.Handle, r.Size, r.Offset, r.Dir)
}
@ -1396,7 +1500,7 @@ func (r *ReadRequest) String() string {
// Respond replies to the request with the given response.
func (r *ReadRequest) Respond(resp *ReadResponse) {
out := &outHeader{Unique: uint64(r.ID)}
r.Conn.respondData(out, unsafe.Sizeof(*out), resp.Data)
r.respondData(out, unsafe.Sizeof(*out), resp.Data)
}
// A ReadResponse is the response to a ReadRequest.
@ -1429,6 +1533,8 @@ type ReleaseRequest struct {
LockOwner uint32
}
var _ = Request(&ReleaseRequest{})
func (r *ReleaseRequest) String() string {
return fmt.Sprintf("Release [%s] %#x fl=%v rfl=%v owner=%#x", &r.Header, r.Handle, r.Flags, r.ReleaseFlags, r.LockOwner)
}
@ -1436,7 +1542,7 @@ func (r *ReleaseRequest) String() string {
// Respond replies to the request, indicating that the handle has been released.
func (r *ReleaseRequest) Respond() {
out := &outHeader{Unique: uint64(r.ID)}
r.Conn.respond(out, unsafe.Sizeof(*out))
r.respond(out, unsafe.Sizeof(*out))
}
// A DestroyRequest is sent by the kernel when unmounting the file system.
@ -1446,6 +1552,8 @@ type DestroyRequest struct {
Header `json:"-"`
}
var _ = Request(&DestroyRequest{})
func (r *DestroyRequest) String() string {
return fmt.Sprintf("Destroy [%s]", &r.Header)
}
@ -1453,7 +1561,7 @@ func (r *DestroyRequest) String() string {
// Respond replies to the request.
func (r *DestroyRequest) Respond() {
out := &outHeader{Unique: uint64(r.ID)}
r.Conn.respond(out, unsafe.Sizeof(*out))
r.respond(out, unsafe.Sizeof(*out))
}
// A ForgetRequest is sent by the kernel when forgetting about r.Node
@ -1463,6 +1571,8 @@ type ForgetRequest struct {
N uint64
}
var _ = Request(&ForgetRequest{})
func (r *ForgetRequest) String() string {
return fmt.Sprintf("Forget [%s] %d", &r.Header, r.N)
}
@ -1470,6 +1580,7 @@ func (r *ForgetRequest) String() string {
// Respond replies to the request, indicating that the forgetfulness has been recorded.
func (r *ForgetRequest) Respond() {
// Don't reply to forget messages.
r.noResponse()
}
// A Dirent represents a single directory entry.
@ -1562,6 +1673,8 @@ type WriteRequest struct {
Flags WriteFlags
}
var _ = Request(&WriteRequest{})
func (r *WriteRequest) String() string {
return fmt.Sprintf("Write [%s] %#x %d @%d fl=%v", &r.Header, r.Handle, len(r.Data), r.Offset, r.Flags)
}
@ -1589,7 +1702,7 @@ func (r *WriteRequest) Respond(resp *WriteResponse) {
outHeader: outHeader{Unique: uint64(r.ID)},
Size: uint32(resp.Size),
}
r.Conn.respond(&out.outHeader, unsafe.Sizeof(*out))
r.respond(&out.outHeader, unsafe.Sizeof(*out))
}
// A WriteResponse replies to a write indicating how many bytes were written.
@ -1621,6 +1734,8 @@ type SetattrRequest struct {
Flags uint32 // see chflags(2)
}
var _ = Request(&SetattrRequest{})
func (r *SetattrRequest) String() string {
var buf bytes.Buffer
fmt.Fprintf(&buf, "Setattr [%s]", &r.Header)
@ -1680,7 +1795,7 @@ func (r *SetattrRequest) Respond(resp *SetattrResponse) {
AttrValidNsec: uint32(resp.AttrValid % time.Second / time.Nanosecond),
Attr: resp.Attr.attr(),
}
r.Conn.respond(&out.outHeader, unsafe.Sizeof(*out))
r.respond(&out.outHeader, unsafe.Sizeof(*out))
}
// A SetattrResponse is the response to a SetattrRequest.
@ -1703,6 +1818,8 @@ type FlushRequest struct {
LockOwner uint64
}
var _ = Request(&FlushRequest{})
func (r *FlushRequest) String() string {
return fmt.Sprintf("Flush [%s] %#x fl=%#x lk=%#x", &r.Header, r.Handle, r.Flags, r.LockOwner)
}
@ -1710,16 +1827,19 @@ func (r *FlushRequest) String() string {
// Respond replies to the request, indicating that the flush succeeded.
func (r *FlushRequest) Respond() {
out := &outHeader{Unique: uint64(r.ID)}
r.Conn.respond(out, unsafe.Sizeof(*out))
r.respond(out, unsafe.Sizeof(*out))
}
// A RemoveRequest asks to remove a file or directory.
// A RemoveRequest asks to remove a file or directory from the
// directory r.Node.
type RemoveRequest struct {
Header `json:"-"`
Name string // name of extended attribute
Name string // name of the entry to remove
Dir bool // is this rmdir?
}
var _ = Request(&RemoveRequest{})
func (r *RemoveRequest) String() string {
return fmt.Sprintf("Remove [%s] %q dir=%v", &r.Header, r.Name, r.Dir)
}
@ -1727,7 +1847,7 @@ func (r *RemoveRequest) String() string {
// Respond replies to the request, indicating that the file was removed.
func (r *RemoveRequest) Respond() {
out := &outHeader{Unique: uint64(r.ID)}
r.Conn.respond(out, unsafe.Sizeof(*out))
r.respond(out, unsafe.Sizeof(*out))
}
// A SymlinkRequest is a request to create a symlink making NewName point to Target.
@ -1736,6 +1856,8 @@ type SymlinkRequest struct {
NewName, Target string
}
var _ = Request(&SymlinkRequest{})
func (r *SymlinkRequest) String() string {
return fmt.Sprintf("Symlink [%s] from %q to target %q", &r.Header, r.NewName, r.Target)
}
@ -1752,7 +1874,7 @@ func (r *SymlinkRequest) Respond(resp *SymlinkResponse) {
AttrValidNsec: uint32(resp.AttrValid % time.Second / time.Nanosecond),
Attr: resp.Attr.attr(),
}
r.Conn.respond(&out.outHeader, unsafe.Sizeof(*out))
r.respond(&out.outHeader, unsafe.Sizeof(*out))
}
// A SymlinkResponse is the response to a SymlinkRequest.
@ -1765,13 +1887,15 @@ type ReadlinkRequest struct {
Header `json:"-"`
}
var _ = Request(&ReadlinkRequest{})
func (r *ReadlinkRequest) String() string {
return fmt.Sprintf("Readlink [%s]", &r.Header)
}
func (r *ReadlinkRequest) Respond(target string) {
out := &outHeader{Unique: uint64(r.ID)}
r.Conn.respondData(out, unsafe.Sizeof(*out), []byte(target))
r.respondData(out, unsafe.Sizeof(*out), []byte(target))
}
// A LinkRequest is a request to create a hard link.
@ -1781,6 +1905,8 @@ type LinkRequest struct {
NewName string
}
var _ = Request(&LinkRequest{})
func (r *LinkRequest) String() string {
return fmt.Sprintf("Link [%s] node %d to %q", &r.Header, r.OldNode, r.NewName)
}
@ -1796,7 +1922,7 @@ func (r *LinkRequest) Respond(resp *LookupResponse) {
AttrValidNsec: uint32(resp.AttrValid % time.Second / time.Nanosecond),
Attr: resp.Attr.attr(),
}
r.Conn.respond(&out.outHeader, unsafe.Sizeof(*out))
r.respond(&out.outHeader, unsafe.Sizeof(*out))
}
// A RenameRequest is a request to rename a file.
@ -1806,13 +1932,15 @@ type RenameRequest struct {
OldName, NewName string
}
var _ = Request(&RenameRequest{})
func (r *RenameRequest) String() string {
return fmt.Sprintf("Rename [%s] from %q to dirnode %d %q", &r.Header, r.OldName, r.NewDir, r.NewName)
}
func (r *RenameRequest) Respond() {
out := &outHeader{Unique: uint64(r.ID)}
r.Conn.respond(out, unsafe.Sizeof(*out))
r.respond(out, unsafe.Sizeof(*out))
}
type MknodRequest struct {
@ -1822,6 +1950,8 @@ type MknodRequest struct {
Rdev uint32
}
var _ = Request(&MknodRequest{})
func (r *MknodRequest) String() string {
return fmt.Sprintf("Mknod [%s] Name %q mode %v rdev %d", &r.Header, r.Name, r.Mode, r.Rdev)
}
@ -1837,7 +1967,7 @@ func (r *MknodRequest) Respond(resp *LookupResponse) {
AttrValidNsec: uint32(resp.AttrValid % time.Second / time.Nanosecond),
Attr: resp.Attr.attr(),
}
r.Conn.respond(&out.outHeader, unsafe.Sizeof(*out))
r.respond(&out.outHeader, unsafe.Sizeof(*out))
}
type FsyncRequest struct {
@ -1848,13 +1978,15 @@ type FsyncRequest struct {
Dir bool
}
var _ = Request(&FsyncRequest{})
func (r *FsyncRequest) String() string {
return fmt.Sprintf("Fsync [%s] Handle %v Flags %v", &r.Header, r.Handle, r.Flags)
}
func (r *FsyncRequest) Respond() {
out := &outHeader{Unique: uint64(r.ID)}
r.Conn.respond(out, unsafe.Sizeof(*out))
r.respond(out, unsafe.Sizeof(*out))
}
// An InterruptRequest is a request to interrupt another pending request. The
@ -1864,8 +1996,11 @@ type InterruptRequest struct {
IntrID RequestID // ID of the request to be interrupt.
}
var _ = Request(&InterruptRequest{})
func (r *InterruptRequest) Respond() {
// nothing to do here
r.noResponse()
}
func (r *InterruptRequest) String() string {
@ -1880,6 +2015,8 @@ type XXXRequest struct {
xxx
}
var _ = Request(&XXXRequest{})
func (r *XXXRequest) String() string {
return fmt.Sprintf("XXX [%s] xxx", &r.Header)
}
@ -1890,7 +2027,7 @@ func (r *XXXRequest) Respond(resp *XXXResponse) {
outHeader: outHeader{Unique: uint64(r.ID)},
xxx,
}
r.Conn.respond(&out.outHeader, unsafe.Sizeof(*out))
r.respond(&out.outHeader, unsafe.Sizeof(*out))
}
// A XXXResponse is the response to a XXXRequest.

View File

@ -513,7 +513,7 @@ type writeOut struct {
Padding uint32
}
// The WriteFlags are returned in the WriteResponse.
// The WriteFlags are passed in WriteRequest.
type WriteFlags uint32
func (fl WriteFlags) String() string {

View File

@ -28,7 +28,13 @@ func main() {
}
mountpoint := flag.Arg(0)
c, err := fuse.Mount(mountpoint)
c, err := fuse.Mount(
mountpoint,
fuse.FSName("helloworld"),
fuse.Subtype("hellofs"),
fuse.LocalVolume(),
fuse.VolumeName("Hello world!"),
)
if err != nil {
log.Fatal(err)
}

BIN
Godeps/_workspace/src/bazil.org/fuse/hellofs/hellofs generated vendored Normal file

Binary file not shown.

View File

@ -3,9 +3,11 @@ package fuse
import (
"bytes"
"errors"
"fmt"
"os"
"os/exec"
"strconv"
"strings"
"syscall"
)
@ -50,15 +52,22 @@ func openOSXFUSEDev() (*os.File, error) {
}
}
func callMount(dir string, f *os.File, ready chan<- struct{}, errp *error) error {
func callMount(dir string, conf *MountConfig, f *os.File, ready chan<- struct{}, errp *error) error {
bin := "/Library/Filesystems/osxfusefs.fs/Support/mount_osxfusefs"
for k, v := range conf.options {
if strings.Contains(k, ",") || strings.Contains(v, ",") {
// Silly limitation but the mount helper does not
// understand any escaping. See TestMountOptionCommaError.
return fmt.Errorf("mount options cannot contain commas on OS X: %q=%q", k, v)
}
}
cmd := exec.Command(
bin,
"-o", conf.getOptions(),
// Tell osxfuse-kext how large our buffer is. It must split
// writes larger than this into multiple writes.
//
// TODO add buffer reuse, bump this up significantly
//
// OSXFUSE seems to ignore InitResponse.MaxWrite, and uses
// this instead.
"-o", "iosize="+strconv.FormatUint(maxWrite, 10),
@ -95,7 +104,7 @@ func callMount(dir string, f *os.File, ready chan<- struct{}, errp *error) error
return err
}
func mount(dir string, ready chan<- struct{}, errp *error) (*os.File, error) {
func mount(dir string, conf *MountConfig, ready chan<- struct{}, errp *error) (*os.File, error) {
f, err := openOSXFUSEDev()
if err == errNotLoaded {
err = loadOSXFUSE()
@ -108,7 +117,7 @@ func mount(dir string, ready chan<- struct{}, errp *error) (*os.File, error) {
if err != nil {
return nil, err
}
err = callMount(dir, f, ready, errp)
err = callMount(dir, conf, f, ready, errp)
if err != nil {
f.Close()
return nil, err

View File

@ -8,7 +8,7 @@ import (
"syscall"
)
func mount(dir string, ready chan<- struct{}, errp *error) (fusefd *os.File, err error) {
func mount(dir string, conf *MountConfig, ready chan<- struct{}, errp *error) (fusefd *os.File, err error) {
// linux mount is never delayed
close(ready)
@ -19,7 +19,12 @@ func mount(dir string, ready chan<- struct{}, errp *error) (fusefd *os.File, err
defer syscall.Close(fds[0])
defer syscall.Close(fds[1])
cmd := exec.Command("fusermount", "--", dir)
cmd := exec.Command(
"fusermount",
"-o", conf.getOptions(),
"--",
dir,
)
cmd.Env = append(os.Environ(), "_FUSE_COMMFD=3")
writeFile := os.NewFile(uintptr(fds[0]), "fusermount-child-writes")

100
Godeps/_workspace/src/bazil.org/fuse/options.go generated vendored Normal file
View File

@ -0,0 +1,100 @@
package fuse
import (
"errors"
"strings"
)
// MountConfig holds the configuration for a mount operation.
// Use it by passing MountOption values to Mount.
type MountConfig struct {
options map[string]string
}
func escapeComma(s string) string {
s = strings.Replace(s, `\`, `\\`, -1)
s = strings.Replace(s, `,`, `\,`, -1)
return s
}
// getOptions makes a string of options suitable for passing to FUSE
// mount flag `-o`. Returns an empty string if no options were set.
// Any platform specific adjustments should happen before the call.
func (m *MountConfig) getOptions() string {
var opts []string
for k, v := range m.options {
k = escapeComma(k)
if v != "" {
k += "=" + escapeComma(v)
}
opts = append(opts, k)
}
return strings.Join(opts, ",")
}
// MountOption is passed to Mount to change the behavior of the mount.
type MountOption func(*MountConfig) error
// FSName sets the file system name (also called source) that is
// visible in the list of mounted file systems.
func FSName(name string) MountOption {
return func(conf *MountConfig) error {
conf.options["fsname"] = name
return nil
}
}
// Subtype sets the subtype of the mount. The main type is always
// `fuse`. The type in a list of mounted file systems will look like
// `fuse.foo`.
//
// OS X ignores this option.
func Subtype(fstype string) MountOption {
return func(conf *MountConfig) error {
conf.options["subtype"] = fstype
return nil
}
}
// LocalVolume sets the volume to be local (instead of network),
// changing the behavior of Finder, Spotlight, and such.
//
// OS X only. Others ignore this option.
func LocalVolume() MountOption {
return localVolume
}
// VolumeName sets the volume name shown in Finder.
//
// OS X only. Others ignore this option.
func VolumeName(name string) MountOption {
return volumeName(name)
}
var ErrCannotCombineAllowOtherAndAllowRoot = errors.New("cannot combine AllowOther and AllowRoot")
// AllowOther allows other users to access the file system.
//
// Only one of AllowOther or AllowRoot can be used.
func AllowOther() MountOption {
return func(conf *MountConfig) error {
if _, ok := conf.options["allow_root"]; ok {
return ErrCannotCombineAllowOtherAndAllowRoot
}
conf.options["allow_other"] = ""
return nil
}
}
// AllowRoot allows other users to access the file system.
//
// Only one of AllowOther or AllowRoot can be used.
func AllowRoot() MountOption {
return func(conf *MountConfig) error {
if _, ok := conf.options["allow_other"]; ok {
return ErrCannotCombineAllowOtherAndAllowRoot
}
conf.options["allow_root"] = ""
return nil
}
}

13
Godeps/_workspace/src/bazil.org/fuse/options_darwin.go generated vendored Normal file
View File

@ -0,0 +1,13 @@
package fuse
func localVolume(conf *MountConfig) error {
conf.options["local"] = ""
return nil
}
func volumeName(name string) MountOption {
return func(conf *MountConfig) error {
conf.options["volname"] = name
return nil
}
}

View File

@ -0,0 +1,27 @@
package fuse_test
import (
"testing"
"github.com/jbenet/go-ipfs/Godeps/_workspace/src/bazil.org/fuse"
"github.com/jbenet/go-ipfs/Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil"
)
func TestMountOptionCommaError(t *testing.T) {
t.Parallel()
// this test is not tied to FSName, but needs just some option
// with string content
var name = "FuseTest,Marker"
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}},
fuse.FSName(name),
)
switch {
case err == nil:
mnt.Close()
t.Fatal("expected an error about commas")
case err.Error() == `mount options cannot contain commas on OS X: "fsname"="FuseTest,Marker"`:
// all good
default:
t.Fatalf("expected an error about commas, got: %v", err)
}
}

13
Godeps/_workspace/src/bazil.org/fuse/options_linux.go generated vendored Normal file
View File

@ -0,0 +1,13 @@
package fuse
func dummyOption(conf *MountConfig) error {
return nil
}
func localVolume(conf *MountConfig) error {
return nil
}
func volumeName(name string) MountOption {
return dummyOption
}

141
Godeps/_workspace/src/bazil.org/fuse/options_test.go generated vendored Normal file
View File

@ -0,0 +1,141 @@
package fuse_test
import (
"runtime"
"testing"
"github.com/jbenet/go-ipfs/Godeps/_workspace/src/bazil.org/fuse"
"github.com/jbenet/go-ipfs/Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil"
)
func init() {
fstestutil.DebugByDefault()
}
func TestMountOptionFSName(t *testing.T) {
t.Parallel()
const name = "FuseTestMarker"
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}},
fuse.FSName(name),
)
if err != nil {
t.Fatal(err)
}
defer mnt.Close()
info, err := fstestutil.GetMountInfo(mnt.Dir)
if err != nil {
t.Fatal(err)
}
if g, e := info.FSName, name; g != e {
t.Errorf("wrong FSName: %q != %q", g, e)
}
}
func testMountOptionFSNameEvil(t *testing.T, evil string) {
t.Parallel()
var name = "FuseTest" + evil + "Marker"
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}},
fuse.FSName(name),
)
if err != nil {
t.Fatal(err)
}
defer mnt.Close()
info, err := fstestutil.GetMountInfo(mnt.Dir)
if err != nil {
t.Fatal(err)
}
if g, e := info.FSName, name; g != e {
t.Errorf("wrong FSName: %q != %q", g, e)
}
}
func TestMountOptionFSNameEvilComma(t *testing.T) {
if runtime.GOOS == "darwin" {
// see TestMountOptionCommaError for a test that enforces we
// at least give a nice error, instead of corrupting the mount
// options
t.Skip("TODO: OS X gets this wrong, commas in mount options cannot be escaped at all")
}
testMountOptionFSNameEvil(t, ",")
}
func TestMountOptionFSNameEvilSpace(t *testing.T) {
testMountOptionFSNameEvil(t, " ")
}
func TestMountOptionFSNameEvilTab(t *testing.T) {
testMountOptionFSNameEvil(t, "\t")
}
func TestMountOptionFSNameEvilNewline(t *testing.T) {
testMountOptionFSNameEvil(t, "\n")
}
func TestMountOptionFSNameEvilBackslash(t *testing.T) {
testMountOptionFSNameEvil(t, `\`)
}
func TestMountOptionFSNameEvilBackslashDouble(t *testing.T) {
// catch double-unescaping, if it were to happen
testMountOptionFSNameEvil(t, `\\`)
}
func TestMountOptionSubtype(t *testing.T) {
if runtime.GOOS == "darwin" {
t.Skip("OS X does not support Subtype")
}
t.Parallel()
const name = "FuseTestMarker"
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}},
fuse.Subtype(name),
)
if err != nil {
t.Fatal(err)
}
defer mnt.Close()
info, err := fstestutil.GetMountInfo(mnt.Dir)
if err != nil {
t.Fatal(err)
}
if g, e := info.Type, "fuse."+name; g != e {
t.Errorf("wrong Subtype: %q != %q", g, e)
}
}
// TODO test LocalVolume
// TODO test AllowOther; hard because needs system-level authorization
func TestMountOptionAllowOtherThenAllowRoot(t *testing.T) {
t.Parallel()
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}},
fuse.AllowOther(),
fuse.AllowRoot(),
)
if err == nil {
mnt.Close()
}
if g, e := err, fuse.ErrCannotCombineAllowOtherAndAllowRoot; g != e {
t.Fatalf("wrong error: %v != %v", g, e)
}
}
// TODO test AllowRoot; hard because needs system-level authorization
func TestMountOptionAllowRootThenAllowOther(t *testing.T) {
t.Parallel()
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}},
fuse.AllowRoot(),
fuse.AllowOther(),
)
if err == nil {
mnt.Close()
}
if g, e := err, fuse.ErrCannotCombineAllowOtherAndAllowRoot; g != e {
t.Fatalf("wrong error: %v != %v", g, e)
}
}

View File

@ -134,6 +134,8 @@ func daemonFunc(req cmds.Request) (interface{}, error) {
if err != nil {
return nil, err
}
fmt.Printf("IPFS mounted at: %s\n", fsdir)
fmt.Printf("IPNS mounted at: %s\n", nsdir)
}
return nil, listenAndServeAPI(node, req, apiMaddr)
@ -156,27 +158,27 @@ func listenAndServeAPI(node *core.IpfsNode, req cmds.Request, addr ma.Multiaddr)
ifpsHandler := &ipfsHandler{node}
mux.Handle("/ipfs/", ifpsHandler)
done := make(chan struct{}, 1)
defer func() {
done <- struct{}{}
}()
// if the server exits beforehand
var serverError error
serverExited := make(chan struct{})
// go wait until the node dies
go func() {
select {
case <-node.Closed():
case <-done:
return
}
log.Infof("terminating daemon at %s...", addr)
server.Shutdown <- true
fmt.Printf("daemon listening on %s\n", addr)
serverError = server.ListenAndServe(host, mux)
close(serverExited)
}()
fmt.Printf("daemon listening on %s\n", addr)
if err := server.ListenAndServe(host, mux); err != nil {
return err
// wait for server to exit.
select {
case <-serverExited:
// if node being closed before server exits, close server
case <-node.Closing():
log.Infof("daemon at %s terminating...", addr)
server.Shutdown <- true
<-serverExited // now, DO wait until server exits
}
return nil
log.Infof("daemon at %s terminated", addr)
return serverError
}

View File

@ -490,25 +490,32 @@ func (i *cmdInvocation) setupInterruptHandler() {
sig := allInterruptSignals()
go func() {
// first time, try to shut down.
for {
// first time, try to shut down.
// loop because we may be
for count := 0; ; count++ {
<-sig
log.Critical("Received interrupt signal, shutting down...")
n, err := ctx.GetNode()
if err == nil {
go n.Close()
select {
case <-n.Closed():
case <-sig:
log.Critical("Received another interrupt signal, terminating...")
}
if err != nil {
log.Error(err)
log.Critical("Received interrupt signal, terminating...")
os.Exit(-1)
}
os.Exit(0)
}
switch count {
case 0:
log.Critical("Received interrupt signal, shutting down...")
go func() {
n.Close()
log.Info("Gracefully shut down.")
}()
default:
log.Critical("Received another interrupt before graceful shutdown, terminating...")
os.Exit(-1)
}
}
}()
}

View File

@ -143,6 +143,33 @@ baz
},
}
func Mount(node *core.IpfsNode, fsdir, nsdir string) error {
// check if we already have live mounts.
// if the user said "Mount", then there must be something wrong.
// so, close them and try again.
if node.Mounts.Ipfs != nil {
node.Mounts.Ipfs.Unmount()
}
if node.Mounts.Ipns != nil {
node.Mounts.Ipns.Unmount()
}
if err := platformFuseChecks(); err != nil {
return err
}
var err error
if err = doMount(node, fsdir, nsdir); err != nil {
return err
}
return nil
}
var platformFuseChecks = func() error {
return nil
}
func doMount(node *core.IpfsNode, fsdir, nsdir string) error {
fmtFuseErr := func(err error) error {
s := err.Error()
@ -176,8 +203,14 @@ func doMount(node *core.IpfsNode, fsdir, nsdir string) error {
<-done
if err1 != nil || err2 != nil {
fsmount.Close()
nsmount.Close()
log.Infof("error mounting: %s %s", err1, err2)
if fsmount != nil {
fsmount.Unmount()
}
if nsmount != nil {
nsmount.Unmount()
}
if err1 != nil {
return fmtFuseErr(err1)
} else {
@ -190,30 +223,3 @@ func doMount(node *core.IpfsNode, fsdir, nsdir string) error {
node.Mounts.Ipns = nsmount
return nil
}
var platformFuseChecks = func() error {
return nil
}
func Mount(node *core.IpfsNode, fsdir, nsdir string) error {
// check if we already have live mounts.
// if the user said "Mount", then there must be something wrong.
// so, close them and try again.
if node.Mounts.Ipfs != nil {
node.Mounts.Ipfs.Unmount()
}
if node.Mounts.Ipns != nil {
node.Mounts.Ipns.Unmount()
}
if err := platformFuseChecks(); err != nil {
return err
}
var err error
if err = doMount(node, fsdir, nsdir); err != nil {
return err
}
return nil
}

View File

@ -57,6 +57,7 @@ type IpfsNode struct {
PeerHost p2phost.Host // the network host (server+client)
Routing routing.IpfsRouting // the routing system. recommend ipfs-dht
Exchange exchange.Interface // the block exchange + strategy (bitswap)
Blockstore bstore.Blockstore // the block store (lower level)
Blocks *bserv.BlockService // the block service, get/add blocks.
DAG merkledag.DAGService // the merkle dag service, get/add objects.
Resolver *path.Resolver // the path resolution system
@ -94,87 +95,39 @@ func NewIpfsNode(ctx context.Context, cfg *config.Config, online bool) (n *IpfsN
n.ContextGroup = ctxgroup.WithContextAndTeardown(ctx, n.teardown)
ctx = n.ContextGroup.Context()
// setup Peerstore
n.Peerstore = peer.NewPeerstore()
// setup datastore.
if n.Datastore, err = makeDatastore(cfg.Datastore); err != nil {
return nil, debugerror.Wrap(err)
}
// setup local peer identity
n.Identity, n.PrivateKey, err = initIdentity(&n.Config.Identity, online)
// setup local peer ID (private key is loaded in online setup)
if err := n.loadID(); err != nil {
return nil, err
}
n.Blockstore, err = bstore.WriteCached(bstore.NewBlockstore(n.Datastore), kSizeBlockstoreWriteCache)
if err != nil {
return nil, debugerror.Wrap(err)
}
// setup Peerstore
n.Peerstore = peer.NewPeerstore()
if n.PrivateKey != nil {
n.Peerstore.AddPrivKey(n.Identity, n.PrivateKey)
}
blockstore, err := bstore.WriteCached(bstore.NewBlockstore(n.Datastore), kSizeBlockstoreWriteCache)
n.Exchange = offline.Exchange(blockstore)
// setup online services
if online {
// setup the network
listenAddrs, err := listenAddresses(cfg)
if err != nil {
return nil, debugerror.Wrap(err)
if err := n.StartOnlineServices(); err != nil {
return nil, err // debugerror.Wraps.
}
network, err := swarm.NewNetwork(ctx, listenAddrs, n.Identity, n.Peerstore)
if err != nil {
return nil, debugerror.Wrap(err)
}
n.AddChildGroup(network.CtxGroup())
n.PeerHost = p2pbhost.New(network)
// explicitly set these as our listen addrs.
// (why not do it inside inet.NewNetwork? because this way we can
// listen on addresses without necessarily advertising those publicly.)
addrs, err := n.PeerHost.Network().InterfaceListenAddresses()
if err != nil {
return nil, debugerror.Wrap(err)
}
n.Peerstore.AddAddresses(n.Identity, addrs)
// setup diagnostics service
n.Diagnostics = diag.NewDiagnostics(n.Identity, n.PeerHost)
// setup routing service
dhtRouting := dht.NewDHT(ctx, n.PeerHost, n.Datastore)
dhtRouting.Validators[IpnsValidatorTag] = namesys.ValidateIpnsRecord
// TODO(brian): perform this inside NewDHT factory method
n.Routing = dhtRouting
n.AddChildGroup(dhtRouting)
// setup exchange service
const alwaysSendToPeer = true // use YesManStrategy
bitswapNetwork := bsnet.NewFromIpfsHost(n.PeerHost, n.Routing)
n.Exchange = bitswap.New(ctx, n.Identity, bitswapNetwork, blockstore, alwaysSendToPeer)
// TODO consider moving connection supervision into the Network. We've
// discussed improvements to this Node constructor. One improvement
// would be to make the node configurable, allowing clients to inject
// an Exchange, Network, or Routing component and have the constructor
// manage the wiring. In that scenario, this dangling function is a bit
// awkward.
go superviseConnections(ctx, n.PeerHost, dhtRouting, n.Peerstore, n.Config.Bootstrap)
} else {
n.Exchange = offline.Exchange(n.Blockstore)
}
// TODO(brian): when offline instantiate the BlockService with a bitswap
// session that simply doesn't return blocks
n.Blocks, err = bserv.New(blockstore, n.Exchange)
n.Blocks, err = bserv.New(n.Blockstore, n.Exchange)
if err != nil {
return nil, debugerror.Wrap(err)
}
n.DAG = merkledag.NewDAGService(n.Blocks)
n.Namesys = namesys.NewNameSystem(n.Routing)
n.Pinning, err = pin.LoadPinner(n.Datastore, n.DAG)
if err != nil {
n.Pinning = pin.NewPinner(n.Datastore, n.DAG)
@ -185,6 +138,67 @@ func NewIpfsNode(ctx context.Context, cfg *config.Config, online bool) (n *IpfsN
return n, nil
}
func (n *IpfsNode) StartOnlineServices() error {
ctx := n.Context()
if n.PeerHost != nil { // already online.
return debugerror.New("node already online")
}
// load private key
if err := n.loadPrivateKey(); err != nil {
return err
}
// setup the network
listenAddrs, err := listenAddresses(n.Config)
if err != nil {
return debugerror.Wrap(err)
}
network, err := swarm.NewNetwork(ctx, listenAddrs, n.Identity, n.Peerstore)
if err != nil {
return debugerror.Wrap(err)
}
n.AddChildGroup(network.CtxGroup())
n.PeerHost = p2pbhost.New(network)
// explicitly set these as our listen addrs.
// (why not do it inside inet.NewNetwork? because this way we can
// listen on addresses without necessarily advertising those publicly.)
addrs, err := n.PeerHost.Network().InterfaceListenAddresses()
if err != nil {
return debugerror.Wrap(err)
}
n.Peerstore.AddAddresses(n.Identity, addrs)
// setup diagnostics service
n.Diagnostics = diag.NewDiagnostics(n.Identity, n.PeerHost)
// setup routing service
dhtRouting := dht.NewDHT(ctx, n.PeerHost, n.Datastore)
dhtRouting.Validators[IpnsValidatorTag] = namesys.ValidateIpnsRecord
n.Routing = dhtRouting
n.AddChildGroup(dhtRouting)
// setup exchange service
const alwaysSendToPeer = true // use YesManStrategy
bitswapNetwork := bsnet.NewFromIpfsHost(n.PeerHost, n.Routing)
n.Exchange = bitswap.New(ctx, n.Identity, bitswapNetwork, n.Blockstore, alwaysSendToPeer)
// setup name system
// TODO implement an offline namesys that serves only local names.
n.Namesys = namesys.NewNameSystem(n.Routing)
// TODO consider moving connection supervision into the Network. We've
// discussed improvements to this Node constructor. One improvement
// would be to make the node configurable, allowing clients to inject
// an Exchange, Network, or Routing component and have the constructor
// manage the wiring. In that scenario, this dangling function is a bit
// awkward.
go superviseConnections(ctx, n.PeerHost, dhtRouting, n.Peerstore, n.Config.Bootstrap)
return nil
}
func (n *IpfsNode) teardown() error {
if err := n.Datastore.Close(); err != nil {
return err
@ -196,29 +210,40 @@ func (n *IpfsNode) OnlineMode() bool {
return n.onlineMode
}
func initIdentity(cfg *config.Identity, online bool) (peer.ID, ic.PrivKey, error) {
if cfg.PeerID == "" {
return "", nil, debugerror.New("Identity was not set in config (was ipfs init run?)")
func (n *IpfsNode) loadID() error {
if n.Identity != "" {
return debugerror.New("identity already loaded")
}
if len(cfg.PeerID) == 0 {
return "", nil, debugerror.New("No peer ID in config! (was ipfs init run?)")
cid := n.Config.Identity.PeerID
if cid == "" {
return debugerror.New("Identity was not set in config (was ipfs init run?)")
}
if len(cid) == 0 {
return debugerror.New("No peer ID in config! (was ipfs init run?)")
}
id := peer.ID(b58.Decode(cfg.PeerID))
n.Identity = peer.ID(b58.Decode(cid))
return nil
}
// when not online, don't need to parse private keys (yet)
if !online {
return id, nil, nil
func (n *IpfsNode) loadPrivateKey() error {
if n.Identity == "" || n.Peerstore == nil {
return debugerror.New("loaded private key out of order.")
}
sk, err := loadPrivateKey(cfg, id)
if n.PrivateKey != nil {
return debugerror.New("private key already loaded")
}
sk, err := loadPrivateKey(&n.Config.Identity, n.Identity)
if err != nil {
return "", nil, err
return err
}
return id, sk, nil
n.PrivateKey = sk
n.Peerstore.AddPrivKey(n.Identity, n.PrivateKey)
return nil
}
func loadPrivateKey(cfg *config.Identity, id peer.ID) (ic.PrivKey, error) {

View File

@ -69,7 +69,7 @@ func setupIpnsTest(t *testing.T, node *core.IpfsNode) (*core.IpfsNode, *fstest.M
}
}
fs, err := NewIpns(node, node.PrivateKey, "")
fs, err := NewFileSystem(node, node.PrivateKey, "")
if err != nil {
t.Fatal(err)
}

View File

@ -37,7 +37,7 @@ type FileSystem struct {
}
// NewFileSystem constructs new fs using given core.IpfsNode instance.
func NewIpns(ipfs *core.IpfsNode, sk ci.PrivKey, ipfspath string) (*FileSystem, error) {
func NewFileSystem(ipfs *core.IpfsNode, sk ci.PrivKey, ipfspath string) (*FileSystem, error) {
root, err := CreateRoot(ipfs, []ci.PrivKey{sk}, ipfspath)
if err != nil {
return nil, err

View File

@ -1,100 +1,18 @@
// +build linux darwin freebsd
package ipns
import (
"fmt"
"os/exec"
"runtime"
"time"
fuse "github.com/jbenet/go-ipfs/Godeps/_workspace/src/bazil.org/fuse"
fs "github.com/jbenet/go-ipfs/Godeps/_workspace/src/bazil.org/fuse/fs"
core "github.com/jbenet/go-ipfs/core"
mount "github.com/jbenet/go-ipfs/fuse/mount"
)
// Mount mounts an IpfsNode instance at a particular path. It
// serves until the process receives exit signals (to Unmount).
func Mount(ipfs *core.IpfsNode, fpath string, ipfspath string) (mount.Mount, error) {
log.Infof("Mounting ipns at %s...", fpath)
// setup the Mount abstraction.
m := mount.New(ipfs.Context(), fpath)
// go serve the mount
m.Mount(func(m mount.Mount) error {
return internalMount(ipfs, fpath, ipfspath)
}, internalUnmount)
select {
case <-m.Closed():
return nil, fmt.Errorf("failed to mount")
case <-time.After(time.Second):
// assume it worked...
}
// bind the mount (ContextGroup) to the node, so that when the node exits
// the fsclosers are automatically closed.
ipfs.AddChildGroup(m)
return m, nil
}
// mount attempts to mount at the provided FUSE mount point
func internalMount(ipfs *core.IpfsNode, fpath string, ipfspath string) error {
c, err := fuse.Mount(fpath)
// Mount mounts ipns at a given location, and returns a mount.Mount instance.
func Mount(ipfs *core.IpfsNode, ipnsmp, ipfsmp string) (mount.Mount, error) {
fsys, err := NewFileSystem(ipfs, ipfs.PrivateKey, ipfsmp)
if err != nil {
return err
}
defer c.Close()
fsys, err := NewIpns(ipfs, ipfs.PrivateKey, ipfspath)
if err != nil {
return err
return nil, err
}
log.Infof("Mounted ipns at %s.", fpath)
if err := fs.Serve(c, fsys); err != nil {
return err
}
// check if the mount process has an error to report
<-c.Ready
if err := c.MountError; err != nil {
return err
}
return nil
}
// unmount attempts to unmount the provided FUSE mount point, forcibly
// if necessary.
func internalUnmount(m mount.Mount) error {
point := m.MountPoint()
log.Infof("Unmounting ipns at %s...", point)
var cmd *exec.Cmd
switch runtime.GOOS {
case "darwin":
cmd = exec.Command("diskutil", "umount", "force", point)
case "linux":
cmd = exec.Command("fusermount", "-u", point)
default:
return fmt.Errorf("unmount: unimplemented")
}
errc := make(chan error, 1)
go func() {
if err := exec.Command("umount", point).Run(); err == nil {
errc <- err
}
// retry to unmount with the fallback cmd
errc <- cmd.Run()
}()
select {
case <-time.After(1 * time.Second):
return fmt.Errorf("umount timeout")
case err := <-errc:
return err
}
return mount.NewMount(ipfs, fsys, ipnsmp)
}

View File

@ -3,9 +3,12 @@ package mount
import (
"fmt"
"os/exec"
"runtime"
"time"
context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
fuse "github.com/jbenet/go-ipfs/Godeps/_workspace/src/bazil.org/fuse"
fs "github.com/jbenet/go-ipfs/Godeps/_workspace/src/bazil.org/fuse/fs"
ctxgroup "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-ctxgroup"
u "github.com/jbenet/go-ipfs/util"
@ -13,62 +16,119 @@ import (
var log = u.Logger("mount")
var MountTimeout = time.Second * 5
// Mount represents a filesystem mount
type Mount interface {
// MountPoint is the path at which this mount is mounted
MountPoint() string
// Mount function sets up a mount + registers the unmount func
Mount(mount MountFunc, unmount UnmountFunc)
// Unmount calls Close.
// Unmounts the mount
Unmount() error
ctxgroup.ContextGroup
}
// UnmountFunc is a function used to Unmount a mount
type UnmountFunc func(Mount) error
// MountFunc is a function used to Mount a mount
type MountFunc func(Mount) error
// New constructs a new Mount instance. ctx is a context to wait upon,
// the mountpoint is the directory that the mount was mounted at, and unmount
// in an UnmountFunc to perform the unmounting logic.
func New(ctx context.Context, mountpoint string) Mount {
m := &mount{mpoint: mountpoint}
m.ContextGroup = ctxgroup.WithContextAndTeardown(ctx, m.persistentUnmount)
return m
// CtxGroup returns the mount's CtxGroup to be able to link it
// to other processes. Unmount upon closing.
CtxGroup() ctxgroup.ContextGroup
}
// mount implements go-ipfs/fuse/mount
type mount struct {
ctxgroup.ContextGroup
mpoint string
filesys fs.FS
fuseConn *fuse.Conn
// closeErr error
unmount UnmountFunc
mpoint string
cg ctxgroup.ContextGroup
}
// umount is called after the mount is closed.
// TODO this is hacky, make it better.
func (m *mount) persistentUnmount() error {
// no unmount func.
if m.unmount == nil {
// Mount mounts a fuse fs.FS at a given location, and returns a Mount instance.
// parent is a ContextGroup to bind the mount's ContextGroup to.
func NewMount(p ctxgroup.ContextGroup, fsys fs.FS, mountpoint string) (Mount, error) {
conn, err := fuse.Mount(mountpoint)
if err != nil {
return nil, err
}
m := &mount{
mpoint: mountpoint,
fuseConn: conn,
filesys: fsys,
cg: ctxgroup.WithParent(p), // link it to parent.
}
m.cg.SetTeardown(m.unmount)
// launch the mounting process.
if err := m.mount(); err != nil {
m.Unmount() // just in case.
return nil, err
}
return m, nil
}
func (m *mount) mount() error {
log.Infof("Mounting %s", m.MountPoint())
errs := make(chan error, 1)
go func() {
err := fs.Serve(m.fuseConn, m.filesys)
log.Debugf("Mounting %s -- fs.Serve returned (%s)", err)
errs <- err
close(errs)
}()
// wait for the mount process to be done, or timed out.
select {
case <-time.After(MountTimeout):
return fmt.Errorf("Mounting %s timed out.", m.MountPoint())
case err := <-errs:
return err
case <-m.fuseConn.Ready:
}
// check if the mount process has an error to report
if err := m.fuseConn.MountError; err != nil {
return err
}
log.Infof("Mounted %s", m.MountPoint())
return nil
}
// umount is called exactly once to unmount this service.
// note that closing the connection will not always unmount
// properly. If that happens, we bring out the big guns
// (mount.ForceUnmountManyTimes, exec unmount).
func (m *mount) unmount() error {
log.Infof("Unmounting %s", m.MountPoint())
// try unmounting with fuse lib
err := fuse.Unmount(m.MountPoint())
if err == nil {
return nil
}
log.Error("fuse unmount err: %s", err)
// ok try to unmount a whole bunch of times...
for i := 0; i < 34; i++ {
err := m.unmount(m)
if err == nil {
return nil
}
time.Sleep(time.Millisecond * 300)
// try closing the fuseConn
err = m.fuseConn.Close()
if err == nil {
return nil
}
if err != nil {
log.Error("fuse conn error: %s", err)
}
// didnt work.
return fmt.Errorf("Unmount %s failed after 10 seconds of trying.")
// try mount.ForceUnmountManyTimes
if err := ForceUnmountManyTimes(m, 10); err != nil {
return err
}
log.Infof("Seemingly unmounted %s", m.MountPoint())
return nil
}
func (m *mount) CtxGroup() ctxgroup.ContextGroup {
return m.cg
}
func (m *mount) MountPoint() string {
@ -76,17 +136,59 @@ func (m *mount) MountPoint() string {
}
func (m *mount) Unmount() error {
return m.Close()
// call ContextCloser Close(), which calls unmount() exactly once.
return m.cg.Close()
}
func (m *mount) Mount(mount MountFunc, unmount UnmountFunc) {
m.unmount = unmount
// ForceUnmount attempts to forcibly unmount a given mount.
// It does so by calling diskutil or fusermount directly.
func ForceUnmount(m Mount) error {
point := m.MountPoint()
log.Infof("Force-Unmounting %s...", point)
// go serve the mount
m.ContextGroup.AddChildFunc(func(parent ctxgroup.ContextGroup) {
if err := mount(m); err != nil {
log.Error("%s mount: %s", m.MountPoint(), err)
var cmd *exec.Cmd
switch runtime.GOOS {
case "darwin":
cmd = exec.Command("diskutil", "umount", "force", point)
case "linux":
cmd = exec.Command("fusermount", "-u", point)
default:
return fmt.Errorf("unmount: unimplemented")
}
errc := make(chan error, 1)
go func() {
defer close(errc)
// try vanilla unmount first.
if err := exec.Command("umount", point).Run(); err == nil {
return
}
m.Unmount()
})
// retry to unmount with the fallback cmd
errc <- cmd.Run()
}()
select {
case <-time.After(2 * time.Second):
return fmt.Errorf("umount timeout")
case err := <-errc:
return err
}
}
// ForceUnmountManyTimes attempts to forcibly unmount a given mount,
// many times. It does so by calling diskutil or fusermount directly.
// Attempts a given number of times.
func ForceUnmountManyTimes(m Mount, attempts int) error {
var err error
for i := 0; i < attempts; i++ {
err = ForceUnmount(m)
if err == nil {
return err
}
<-time.After(time.Millisecond * 500)
}
return fmt.Errorf("Unmount %s failed after 10 seconds of trying.", m.MountPoint())
}

View File

@ -0,0 +1,14 @@
// +build linux darwin freebsd
package readonly
import (
core "github.com/jbenet/go-ipfs/core"
mount "github.com/jbenet/go-ipfs/fuse/mount"
)
// Mount mounts ipfs at a given location, and returns a mount.Mount instance.
func Mount(ipfs *core.IpfsNode, mountpoint string) (mount.Mount, error) {
fsys := NewFileSystem(ipfs)
return mount.NewMount(ipfs, fsys, mountpoint)
}

View File

@ -5,19 +5,14 @@
package readonly
import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"runtime"
"time"
fuse "github.com/jbenet/go-ipfs/Godeps/_workspace/src/bazil.org/fuse"
fs "github.com/jbenet/go-ipfs/Godeps/_workspace/src/bazil.org/fuse/fs"
proto "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto"
core "github.com/jbenet/go-ipfs/core"
mount "github.com/jbenet/go-ipfs/fuse/mount"
mdag "github.com/jbenet/go-ipfs/merkledag"
uio "github.com/jbenet/go-ipfs/unixfs/io"
ftpb "github.com/jbenet/go-ipfs/unixfs/pb"
@ -158,86 +153,3 @@ func (s *Node) ReadAll(intr fs.Intr) ([]byte, fuse.Error) {
// what if i have a 6TB file? GG RAM.
return ioutil.ReadAll(r)
}
// Mount mounts an IpfsNode instance at a particular path. It
// serves until the process receives exit signals (to Unmount).
func Mount(ipfs *core.IpfsNode, fpath string) (mount.Mount, error) {
log.Infof("Mounting ipfs at %s...", fpath)
// setup the Mount abstraction.
m := mount.New(ipfs.Context(), fpath)
// go serve the mount
m.Mount(func(m mount.Mount) error {
return internalMount(ipfs, m)
}, internalUnmount)
select {
case <-m.Closed():
return nil, fmt.Errorf("failed to mount")
case <-time.After(time.Second):
// assume it worked...
}
// bind the mount (ContextGroup) to the node, so that when the node exits
// the fsclosers are automatically closed.
ipfs.AddChildGroup(m)
return m, nil
}
// mount attempts to mount the provided FUSE mount point
func internalMount(ipfs *core.IpfsNode, m mount.Mount) error {
c, err := fuse.Mount(m.MountPoint())
if err != nil {
return err
}
defer c.Close()
fsys := FileSystem{Ipfs: ipfs}
log.Infof("Mounted ipfs at %s.", m.MountPoint())
if err := fs.Serve(c, fsys); err != nil {
return err
}
// check if the mount process has an error to report
<-c.Ready
if err := c.MountError; err != nil {
m.Unmount()
return err
}
return nil
}
// unmount attempts to unmount the provided FUSE mount point, forcibly
// if necessary.
func internalUnmount(m mount.Mount) error {
point := m.MountPoint()
log.Infof("Unmounting ipfs at %s...", point)
var cmd *exec.Cmd
switch runtime.GOOS {
case "darwin":
cmd = exec.Command("diskutil", "umount", "force", point)
case "linux":
cmd = exec.Command("fusermount", "-u", point)
default:
return fmt.Errorf("unmount: unimplemented")
}
errc := make(chan error, 1)
go func() {
if err := exec.Command("umount", point).Run(); err == nil {
errc <- err
}
// retry to unmount with the fallback cmd
errc <- cmd.Run()
}()
select {
case <-time.After(1 * time.Second):
return fmt.Errorf("umount timeout")
case err := <-errc:
return err
}
}

View File

@ -23,6 +23,10 @@ type routingResolver struct {
// NewRoutingResolver constructs a name resolver using the IPFS Routing system
// to implement SFS-like naming on top.
func NewRoutingResolver(route routing.IpfsRouting) Resolver {
if route == nil {
panic("attempt to create resolver with nil routing system")
}
return &routingResolver{routing: route}
}