diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 61c05dd9a..00f65e466 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -7,7 +7,7 @@ "Deps": [ { "ImportPath": "bazil.org/fuse", - "Rev": "a04507d54fc3610d38ee951402d8c4acab56c7b1" + "Rev": "d62a1291477b51b24becf4def173bd843138c4b6" }, { "ImportPath": "bitbucket.org/kardianos/osext", diff --git a/Godeps/_workspace/src/bazil.org/fuse/debug.go b/Godeps/_workspace/src/bazil.org/fuse/debug.go index 78194ff20..be9f900d5 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/debug.go +++ b/Godeps/_workspace/src/bazil.org/fuse/debug.go @@ -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 diff --git a/Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/doc.go b/Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/doc.go new file mode 100644 index 000000000..d4366cca5 --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/doc.go @@ -0,0 +1 @@ +package fstestutil diff --git a/Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/mounted.go b/Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/mounted.go index 34f3770f5..42240bdbe 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/mounted.go +++ b/Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/mounted.go @@ -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...) } diff --git a/Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/mountinfo.go b/Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/mountinfo.go new file mode 100644 index 000000000..4e410ebd0 --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/mountinfo.go @@ -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) +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/mountinfo_darwin.go b/Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/mountinfo_darwin.go new file mode 100644 index 000000000..dc88b0b06 --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/mountinfo_darwin.go @@ -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 +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/mountinfo_linux.go b/Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/mountinfo_linux.go new file mode 100644 index 000000000..c502cf59b --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/mountinfo_linux.go @@ -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 +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/testfs.go b/Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/testfs.go new file mode 100644 index 000000000..9596804c7 --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/testfs.go @@ -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} } diff --git a/Godeps/_workspace/src/bazil.org/fuse/fs/serve.go b/Godeps/_workspace/src/bazil.org/fuse/fs/serve.go index efaa4f143..f0185140e 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/fs/serve.go +++ b/Godeps/_workspace/src/bazil.org/fuse/fs/serve.go @@ -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) diff --git a/Godeps/_workspace/src/bazil.org/fuse/fs/serve_test.go b/Godeps/_workspace/src/bazil.org/fuse/fs/serve_test.go index dde3ce905..c1468c3b0 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/fs/serve_test.go +++ b/Godeps/_workspace/src/bazil.org/fuse/fs/serve_test.go @@ -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 diff --git a/Godeps/_workspace/src/bazil.org/fuse/fuse.go b/Godeps/_workspace/src/bazil.org/fuse/fuse.go index 7ba9af016..5d6ed81df 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/fuse.go +++ b/Godeps/_workspace/src/bazil.org/fuse/fuse.go @@ -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. diff --git a/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel.go b/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel.go index a8ec12ca4..5fba53dbf 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel.go +++ b/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel.go @@ -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 { diff --git a/Godeps/_workspace/src/bazil.org/fuse/hellofs/hello.go b/Godeps/_workspace/src/bazil.org/fuse/hellofs/hello.go index 68f7c9ab1..7e74f9fed 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/hellofs/hello.go +++ b/Godeps/_workspace/src/bazil.org/fuse/hellofs/hello.go @@ -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) } diff --git a/Godeps/_workspace/src/bazil.org/fuse/hellofs/hellofs b/Godeps/_workspace/src/bazil.org/fuse/hellofs/hellofs new file mode 100644 index 000000000..641c0ef1c Binary files /dev/null and b/Godeps/_workspace/src/bazil.org/fuse/hellofs/hellofs differ diff --git a/Godeps/_workspace/src/bazil.org/fuse/mount_darwin.go b/Godeps/_workspace/src/bazil.org/fuse/mount_darwin.go index d5ab2960a..6253ce82d 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/mount_darwin.go +++ b/Godeps/_workspace/src/bazil.org/fuse/mount_darwin.go @@ -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 diff --git a/Godeps/_workspace/src/bazil.org/fuse/mount_linux.go b/Godeps/_workspace/src/bazil.org/fuse/mount_linux.go index ef95d093d..0748c0a5d 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/mount_linux.go +++ b/Godeps/_workspace/src/bazil.org/fuse/mount_linux.go @@ -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") diff --git a/Godeps/_workspace/src/bazil.org/fuse/options.go b/Godeps/_workspace/src/bazil.org/fuse/options.go new file mode 100644 index 000000000..643a9492d --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/options.go @@ -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 + } +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/options_darwin.go b/Godeps/_workspace/src/bazil.org/fuse/options_darwin.go new file mode 100644 index 000000000..15aedbcfc --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/options_darwin.go @@ -0,0 +1,13 @@ +package fuse + +func localVolume(conf *MountConfig) error { + conf.options["local"] = "" + return nil +} + +func volumeName(name string) MountOption { + return func(conf *MountConfig) error { + conf.options["volname"] = name + return nil + } +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/options_darwin_test.go b/Godeps/_workspace/src/bazil.org/fuse/options_darwin_test.go new file mode 100644 index 000000000..be031f606 --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/options_darwin_test.go @@ -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) + } +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/options_linux.go b/Godeps/_workspace/src/bazil.org/fuse/options_linux.go new file mode 100644 index 000000000..69dd406ba --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/options_linux.go @@ -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 +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/options_test.go b/Godeps/_workspace/src/bazil.org/fuse/options_test.go new file mode 100644 index 000000000..91d615d93 --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/options_test.go @@ -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) + } +} diff --git a/cmd/ipfs/daemon.go b/cmd/ipfs/daemon.go index 66d733e7c..e0a6663cf 100644 --- a/cmd/ipfs/daemon.go +++ b/cmd/ipfs/daemon.go @@ -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 } diff --git a/cmd/ipfs/main.go b/cmd/ipfs/main.go index fb0076e12..4d7f4350c 100644 --- a/cmd/ipfs/main.go +++ b/cmd/ipfs/main.go @@ -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) + } + } }() } diff --git a/core/commands/mount_unix.go b/core/commands/mount_unix.go index a9b816ae0..b06451f9e 100644 --- a/core/commands/mount_unix.go +++ b/core/commands/mount_unix.go @@ -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 -} diff --git a/core/core.go b/core/core.go index b48e80f9c..3a181f8eb 100644 --- a/core/core.go +++ b/core/core.go @@ -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) { diff --git a/fuse/ipns/ipns_test.go b/fuse/ipns/ipns_test.go index 1f65be07b..5811a0533 100644 --- a/fuse/ipns/ipns_test.go +++ b/fuse/ipns/ipns_test.go @@ -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) } diff --git a/fuse/ipns/ipns_unix.go b/fuse/ipns/ipns_unix.go index 3172dd57e..15249c3cd 100644 --- a/fuse/ipns/ipns_unix.go +++ b/fuse/ipns/ipns_unix.go @@ -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 diff --git a/fuse/ipns/mount_unix.go b/fuse/ipns/mount_unix.go index 7365d929a..ea3e499a4 100644 --- a/fuse/ipns/mount_unix.go +++ b/fuse/ipns/mount_unix.go @@ -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) } diff --git a/fuse/mount/mount.go b/fuse/mount/mount.go index 24676cd9c..f7b77bf9e 100644 --- a/fuse/mount/mount.go +++ b/fuse/mount/mount.go @@ -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()) } diff --git a/fuse/readonly/mount_unix.go b/fuse/readonly/mount_unix.go new file mode 100644 index 000000000..d81d51b9c --- /dev/null +++ b/fuse/readonly/mount_unix.go @@ -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) +} diff --git a/fuse/readonly/readonly_unix.go b/fuse/readonly/readonly_unix.go index 2a0e2c43c..6a93c7cc3 100644 --- a/fuse/readonly/readonly_unix.go +++ b/fuse/readonly/readonly_unix.go @@ -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 - } -} diff --git a/namesys/routing.go b/namesys/routing.go index 709f9424c..b57d2c601 100644 --- a/namesys/routing.go +++ b/namesys/routing.go @@ -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} }