From 54caf187c8f3a2309d407c97e97a6740f2290b53 Mon Sep 17 00:00:00 2001 From: DeedleFake Date: Thu, 17 Oct 2019 23:46:00 -0400 Subject: [PATCH] p9: fix Remote usage of "." in walks (#67) * p9: Don't walk to "." in Remote. * multiple: Fix some godoc comments. * p9: Remove another possible ".". * cmd/p9: Don't include `mount` command on systems that don't have FUSE. * cmd/p9: Add some error logging to `mount` and fix EOFs when reading. --- cmd/p9/mount.go | 21 ++++++++++++++++++--- dir.go | 11 ++++++++--- doc.go | 50 +++++++++++++++++++++++++++++++++++++++---------- fs.go | 12 ++++++------ go.mod | 1 + go.sum | 5 +++++ proto/server.go | 4 ++-- remote.go | 12 +++++++++--- 8 files changed, 89 insertions(+), 27 deletions(-) diff --git a/cmd/p9/mount.go b/cmd/p9/mount.go index 4850ccb..eb979a2 100644 --- a/cmd/p9/mount.go +++ b/cmd/p9/mount.go @@ -1,9 +1,14 @@ +// +build linux darwin freebsd + package main import ( "context" + "errors" "flag" "fmt" + "io" + "log" "path" "bazil.org/fuse" @@ -99,12 +104,12 @@ func (node *fuseNode) flags(f fuse.OpenFlags) (flags uint8) { func (node *fuseNode) Attr(ctx context.Context, attr *fuse.Attr) error { s, err := node.n.Stat(node.p) if err != nil { + fmt.Printf("Error statting file: %v", err) return err } attr.Inode = s.Path attr.Size = s.Length - attr.Blocks = s.Length / 512 attr.Atime = s.ATime attr.Mtime = s.MTime attr.Mode = s.FileMode.OS() @@ -124,7 +129,11 @@ func (node *fuseNode) Lookup(ctx context.Context, name string) (fs.Node, error) func (node *fuseNode) Open(ctx context.Context, req *fuse.OpenRequest, rsp *fuse.OpenResponse) (fs.Handle, error) { n, err := node.n.Open(node.p, node.flags(req.Flags)) - return &fuseNode{n: n}, err + if err != nil { + log.Printf("Error opening file: %v", err) + return nil, err + } + return &fuseNode{n: n}, nil } func (node *fuseNode) direntType(m p9.FileMode) fuse.DirentType { @@ -142,18 +151,24 @@ func (node *fuseNode) direntType(m p9.FileMode) fuse.DirentType { func (node *fuseNode) Read(ctx context.Context, req *fuse.ReadRequest, rsp *fuse.ReadResponse) error { if req.Dir { + log.Printf("Tried to read file as a directory") return fmt.Errorf("%#v", req) } buf := make([]byte, req.Size) n, err := node.n.ReadAt(buf, req.Offset) rsp.Data = buf[:n] - return err + if (err != nil) && !errors.Is(err, io.EOF) { + log.Printf("Error reading file: %v", err) + return err + } + return nil } func (node *fuseNode) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { e, err := node.n.Readdir() if err != nil { + log.Printf("Error reading directory: %v", err) return nil, err } diff --git a/dir.go b/dir.go index 461b915..fca0101 100644 --- a/dir.go +++ b/dir.go @@ -26,7 +26,12 @@ func (d Dir) Stat(p string) (DirEntry, error) { return DirEntry{}, err } - return infoToEntry(fi), nil + e := infoToEntry(fi) + if e.EntryName == "." { + e.EntryName = "" + } + + return e, nil } // WriteStat implements Attachment.WriteStat. @@ -198,8 +203,8 @@ func (ro readOnlyAttachment) Remove(path string) error { return errors.New("read-only filesystem") } -// AuthFS allows simple wrapping and overwriting of the Auth() and -// Attach() methods of an existing FileSystem implementation, allowing +// AuthFS allows simple wrapping and overwriting of the Auth and +// Attach methods of an existing FileSystem implementation, allowing // the user to add authentication support to a FileSystem that does // not have it, or to change the implementation of that support for // FileSystems that do. diff --git a/doc.go b/doc.go index 692be18..aa6e461 100644 --- a/doc.go +++ b/doc.go @@ -1,8 +1,17 @@ -// Package p9 contains an implementation of 9P, the Plan 9 from Bell Labs file protocol. +// Package p9 contains an implementation of 9P, the Plan 9 from Bell +// Labs file protocol. // -// The package provides high-level APIs for both creating 9P servers and connecting to those servers as a client. Although it abstracts away a lot of the complexity of 9P, some familiarity with the protocol is advised. Like "net/http", it exposes a fair amount of the inner workings of the package so that a user can opt to build their own high-level implementation on top of it. +// The package provides high-level APIs for both creating 9P servers +// and connecting to those servers as a client. Although it abstracts +// away a lot of the complexity of 9P, some familiarity with the +// protocol is advised. Like "net/http", it exposes a fair amount of +// the inner workings of the package so that a user can opt to build +// their own high-level implementation on top of it. // -// The primary concept that the user should understand is that in 9P, everything is referenced relative to previously referenced objects. For example, a typical 9P exchange, skipping version negotiation and authentication, might look something like this: +// The primary concept that the user should understand is that in 9P, +// everything is referenced relative to previously referenced objects. +// For example, a typical 9P exchange, skipping version negotiation +// and authentication, might look something like this: // // attach to filesystem "/" and call it 1 // navigate to file at "some/path" relative to 1 and call it 2 @@ -10,9 +19,14 @@ // open file 3 // read from file 3 // -// This package attempts to completely abstract away the navigation aspects of 9P, but a lot of things are still relative to others. For example, opening a file on the server from the client is done by calling the Open() method on an already existing file reference and passing it a path. +// This package attempts to completely abstract away the navigation +// aspects of 9P, but a lot of things are still relative to others. +// For example, opening a file on the server from the client is done +// by calling the Open method on an already existing file reference +// and passing it a path. // -// The Client type provides a series of functionality that allows the user to connect to 9P servers. Here's an example of its use: +// The Client type provides a series of functionality that allows the +// user to connect to 9P servers. Here's an example of its use: // // c, _ := p9.Dial("tcp", addr) // defer c.Close() @@ -25,9 +39,25 @@ // defer file.Close() // buf, _ := ioutil.ReadAll(file) // -// The client is split into two main types: Client and Remote. Client provides the basic functionality for establishing a connection, performing authentication, and attaching to file hierarchies. Remote provides functionality for opening and creating files, getting information about them, and reading from and writing to them. They behave similarly to files themselves, implementing many of the same interfaces that os.File implements. -// -// The server works similarly to the "net/http" package, but, due to the major differences in the protocol being handled, is quite a bit more complicated. At the top level, there is a ListenAndServe() function, much like what is provided by "net/http". For most cases, the user can provide a filesystem by implementing the FileSystem interface and passing an instance of their implementation to the FSConnHandler() function to get a handler to pass to ListenAndServe(). -// -// If the user only wants to serve local files, the Dir type provides a pre-built implementation of FileSystem that does just that. Similarly, the AuthFS type allows the user to add the ability to authenticate to a FileSystem implementation that otherwise has none, such as the aforementioned Dir. +// The client is split into two main types: Client and Remote. Client +// provides the basic functionality for establishing a connection, +// performing authentication, and attaching to file hierarchies. +// Remote provides functionality for opening and creating files, +// getting information about them, and reading from and writing to +// them. They behave similarly to files themselves, implementing many +// of the same interfaces that os.File implements. +// +// The server works similarly to the "net/http" package, but, due to +// the major differences in the protocol being handled, is quite a bit +// more complicated. At the top level, there is a ListenAndServe +// function, much like what is provided by "net/http". For most cases, +// the user can provide a filesystem by implementing the FileSystem +// interface and passing an instance of their implementation to the +// FSConnHandler function to get a handler to pass to ListenAndServe. +// +// If the user only wants to serve local files, the Dir type provides +// a pre-built implementation of FileSystem that does just that. +// Similarly, the AuthFS type allows the user to add the ability to +// authenticate to a FileSystem implementation that otherwise has +// none, such as the aforementioned Dir. package p9 diff --git a/fs.go b/fs.go index 5bc8409..ba34594 100644 --- a/fs.go +++ b/fs.go @@ -34,10 +34,10 @@ type FileSystem interface { // // All paths passed to the methods of this system begin with the aname // used to attach the attachment, use forward slashes as separators, -// and have been cleaned using path.Clean(). For example, if the -// client attaches using the aname "/example" and then tries to open -// the file located at "some/other/example", the Open() method will be -// called with the path "/example/some/other/example". +// and have been cleaned using path.Clean. For example, if the client +// attaches using the aname "/example" and then tries to open the file +// located at "some/other/example", the Open method will be called +// with the path "/example/some/other/example". type Attachment interface { // Stat returns a DirEntry giving info about the file or directory // at the given path. If an error is returned, the text of the error @@ -147,7 +147,7 @@ type fsHandler struct { // // BUG: Tflush requests are not currently handled at all by this // implementation due to no clear method of stopping a pending call to -// ReadAt() or WriteAt(). +// ReadAt or WriteAt. func FSHandler(fs FileSystem, msize uint32) proto.MessageHandler { return &fsHandler{ fs: fs, @@ -157,7 +157,7 @@ func FSHandler(fs FileSystem, msize uint32) proto.MessageHandler { } } -// FSConnHandler returns a ConnHandler that calls FSHandler() to +// FSConnHandler returns a ConnHandler that calls FSHandler to // generate MessageHandlers. // // The returned ConnHandler implementation will print debug messages diff --git a/go.mod b/go.mod index b20196e..967ac75 100644 --- a/go.mod +++ b/go.mod @@ -5,4 +5,5 @@ go 1.13 require ( bazil.org/fuse v0.0.0-20180421153158-65cc252bf669 golang.org/x/net v0.0.0-20191014212845-da9a3fd4c582 // indirect + golang.org/x/tools v0.0.0-20191018000036-341939e08647 // indirect ) diff --git a/go.sum b/go.sum index 4f5bc9c..baec35d 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,13 @@ bazil.org/fuse v0.0.0-20180421153158-65cc252bf669 h1:FNCRpXiquG1aoyqcIWVFmpTSKVcx2bQD38uZZeGtdlw= bazil.org/fuse v0.0.0-20180421153158-65cc252bf669/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191014212845-da9a3fd4c582 h1:p9xBe/w/OzkeYVKm234g55gMdD1nSIooTir5kV11kfA= golang.org/x/net v0.0.0-20191014212845-da9a3fd4c582/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20191018000036-341939e08647 h1:kfteFQIbFl9R5RwgjPgqDtpdeSnP/IfGo1R0ngom5vs= +golang.org/x/tools v0.0.0-20191018000036-341939e08647/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/proto/server.go b/proto/server.go index 9d22a84..fb21910 100644 --- a/proto/server.go +++ b/proto/server.go @@ -42,7 +42,7 @@ func Serve(lis net.Listener, p Proto, connHandler ConnHandler) (err error) { } // ListenAndServe is a convenience function that establishes a -// listener, via net.Listen(), and then calls Serve(). +// listener, via net.Listen, and then calls Serve. func ListenAndServe(network, addr string, p Proto, connHandler ConnHandler) (rerr error) { lis, err := net.Listen(network, addr) if err != nil { @@ -130,7 +130,7 @@ func (h ConnHandlerFunc) MessageHandler() MessageHandler { // MessageHandler handles messages for a single client connection. // -// If a MessageHandler also implements io.Closer, then Close() will be +// If a MessageHandler also implements io.Closer, then Close will be // called when the connection ends. Its return value is ignored. type MessageHandler interface { // HandleMessage is passed received messages from the client. Its diff --git a/remote.go b/remote.go index 799d02b..f860b93 100644 --- a/remote.go +++ b/remote.go @@ -38,6 +38,9 @@ func (file *Remote) walk(p string) (*Remote, error) { if w[0] != "/" { w = strings.Split(w[0], "/") } + if (len(w) == 1) && (w[0] == ".") { + w = nil + } rsp, err := file.client.Send(&Twalk{ FID: file.fid, NewFID: fid, @@ -48,7 +51,10 @@ func (file *Remote) walk(p string) (*Remote, error) { } walk := rsp.(*Rwalk) - qid := walk.WQID[len(walk.WQID)-1] + qid := file.qid + if len(walk.WQID) != 0 { + qid = walk.WQID[len(walk.WQID)-1] + } if len(walk.WQID) != len(w) { qid = QID{ Type: 0xFF, @@ -179,7 +185,7 @@ func (file *Remote) Seek(offset int64, whence int) (int64, error) { } // Read reads from the file at the internally-tracked offset. For more -// information, see ReadAt(). +// information, see ReadAt. func (file *Remote) Read(buf []byte) (int, error) { file.m.Lock() defer file.m.Unlock() @@ -243,7 +249,7 @@ func (file *Remote) ReadAt(buf []byte, off int64) (int, error) { } // Write writes to the file at the internally-tracked offset. For more -// information, see WriteAt(). +// information, see WriteAt. func (file *Remote) Write(data []byte) (int, error) { file.m.Lock() defer file.m.Unlock()