Documentation ¶
Overview ¶
Package styx serves network filesystems using the 9P2000 protocol.
The styx package provides types and routines for implementing 9P servers. The files served may reflect real files on the host operating system, or an in-memory filesystem, or bi-directional RPC endpoints. Regardless, the protocol operations used to access these files are the same.
The ListenAndServe and ListenAndServeTLS functions run 9P servers bound to a TCP port. To create a 9P server, define a type that implements the Serve9P method. The HandlerFunc type allows regular functions to be used:
fs := styx.HandlerFunc(func(s *styx.Session) { for s.Next() { switch msg := s.Request().(type) { case styx.Twalk: msg.Rwalk(os.Stat(msg.Path())) case styx.Topen: msg.Ropen(os.OpenFile(msg.Path(), msg.Flag, 0777)) case styx.Tstat: msg.Rstat(os.Stat(msg.Path())) } } }) styx.ListenAndServe(":564", fs)
Multiple handlers can be overlaid using the Stack function.
echo := styx.HandlerFunc(func(s *styx.Session) { for s.Next() { log.Printf("%s %q %s", s.User, s.Access, s.Request()) } log.Printf("session %s %q ended", s.User, s.Access) }) styx.ListenAndServe(":564", styx.Stack(echo, handler))
Handlers may pass data downstream using a message's WithContext method:
sessionid := styx.HandlerFunc(func(s *styx.Session) { uuid := rand.Int63() for s.Next() { msg := s.Request() ctx := context.WithValue(msg.Context(), "session", uuid) s.UpdateRequest(msg.WithContext(ctx)) } }) styx.ListenAndServe(":564", styx.Stack(sessionid, echo, fs))
Example ¶
package main import ( "io" "log" "os" "time" "aqwari.net/net/styx" ) type emptyDir struct{} func (emptyDir) Readdir(n int) ([]os.FileInfo, error) { return nil, io.EOF } func (emptyDir) Mode() os.FileMode { return os.ModeDir | 0777 } func (emptyDir) IsDir() bool { return true } func (emptyDir) ModTime() time.Time { return time.Now() } func (emptyDir) Name() string { return "" } func (emptyDir) Size() int64 { return 0 } func (emptyDir) Sys() interface{} { return nil } func main() { // Run a file server that creates directories (and only directories) // on-demand, as a client walks to them. h := styx.HandlerFunc(func(s *styx.Session) { for s.Next() { switch t := s.Request().(type) { case styx.Tstat: t.Rstat(emptyDir{}, nil) case styx.Twalk: t.Rwalk(emptyDir{}, nil) case styx.Topen: t.Ropen(emptyDir{}, nil) } } }) log.Fatal(styx.ListenAndServe(":564", h)) }
Output:
Index ¶
- func ListenAndServe(addr string, handler Handler) error
- func ListenAndServeTLS(addr string, certFile, keyFile string, handler Handler) error
- type AuthFunc
- type AuthOpenFunc
- type Channel
- type Directory
- type Handler
- type HandlerFunc
- type Logger
- type OwnerInfo
- type Request
- type Server
- type Session
- type Tchmod
- type Tchown
- type Tcreate
- type Topen
- type Tremove
- type Trename
- type Tstat
- type Tsync
- type Ttruncate
- type Tutimes
- type Twalk
- Bugs
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ListenAndServe ¶
ListenAndServe listens on the specified TCP address, and then calls Serve with handler to handle requests of incoming connections.
func ListenAndServeTLS ¶
ListenAndServeTLS listens on the specified TCP address for incoming TLS connections. certFile must be a valid x509 certificate in PEM format, concatenated with any intermediate and CA certificates.
Types ¶
type AuthFunc ¶
An AuthFunc is used to authenticate a user to a 9P server. The authentication protocol itself is tunnelled over 9P via read and write operations to a special file, and is outside the scope of the 9P protocol.
An AuthFunc must determine that a client is authorized to start a 9P session to the file tree specified by the access parameter. The Auth method may receive and send data over rwc. Alternatively, additional information can be passed through the Channel's context for external authentication. Notably, the Conn method of the Channel can be used to access the underlying network connection, in order to authenticate based on TLS certificates, unix uid values (on a unix socket), etc.
An AuthFunc must return a non-nil error if authentication fails. The error may be sent to the client and should not contain any sensitive information. If authentication succeeds, an AuthFunc must return nil.
Existing AuthFunc implementations can be found in the styxauth package.
type AuthOpenFunc ¶
type AuthOpenFunc func() (interface{}, error)
type Channel ¶
type Channel struct { context.Context io.ReadWriteCloser }
A Channel provides authentication methods with a bidirectional channel between the client and server, along with any contextual information recorded by the server. Of note is the "conn" value, which returns the underlying net.Conn value for the network connection.
type Directory ¶
In the 9P protocol, a directory is simply a file that returns zero or more styxproto.Stat structures when read. Types that implement the Directory interface can avoid marshalling styxproto.Stat methods in the Read methods. The Readdir method should return up to n os.FileInfo values, based on the contents of the given directory. Further calls to Readdir should pick up where the previous call left off.
If n <= 0, Readdir should return os.FileInfo values for all files in the directory.
type Handler ¶
type Handler interface {
Serve9P(*Session)
}
Types implementing the Handler interface can receive and respond to 9P requests from clients.
When a client connects to the server and starts a session, a new goroutine is created running the handler's Serve9P method. Each 9P message can be retrieved using the Session's Next and Request methods. Serve9P is expected to last for the duration of the 9P session; if the client ends the session, the Session's Next method will return false. If the Serve9P method exits prematurely, all open files and other resources associated with that session are released, and any further requests for that session will result in an error.
The Serve9P method is not required to answer every type of 9P message. If an existing request is unanswered when Serve9P fetches the next request, the styx package will reply to the client with a default response. The documentation for each request type notes its default response.
In practice, a Handler is usually composed of a for loop and a type switch, like so:
func (srv *Srv) Serve9P(s *styx.Session) { for s.Next() { switch msg := s.Request().(type) { case styx.Twalk: if (srv.exists(msg.Path()) { msg.Rwalk(srv.filemode(msg.Path()) } else { msg.Rerror("%s does not exist", msg.Path()) } case styx.Topen: msg.Ropen(srv.getfile(msg.Path())) case styx.Tcreate: msg.Rcreate(srv.newfile(msg.Path()) } } }
Possible message types are listed in the documentation for the Request type.
func Stack ¶
Stack combines multiple handlers into one. When a new message is received from the client, it is passed to each handler, from left to right, until a response is sent. If no response is sent. by any handlers in the stack, the documented default response for that message type will be sent to the client. Handlers may use the UpdateRequest to pass information to downstream handlers.
Example ¶
package main import ( "context" "fmt" "sync/atomic" "aqwari.net/net/styx" ) func main() { // Associate a session ID with each session var sessionID int64 sessionid := styx.HandlerFunc(func(s *styx.Session) { id := atomic.AddInt64(&sessionID, 1) for s.Next() { req := s.Request() ctx := context.WithValue(req.Context(), "session", id) s.UpdateRequest(req.WithContext(ctx)) } }) // echo requests to stdout echo := styx.HandlerFunc(func(s *styx.Session) { for s.Next() { req := s.Request() id := req.Context().Value("session") fmt.Printf("session %v user %q %q %T %s", id, s.User, s.Access, req, req.Path()) } }) // Disallow removal of any files blockops := styx.HandlerFunc(func(s *styx.Session) { for s.Next() { if t, ok := s.Request().(styx.Tremove); ok { t.Rerror("permission denied") } } }) handler := styx.Stack(sessionid, echo, blockops) styx.ListenAndServe(":564", handler) }
Output:
type HandlerFunc ¶
type HandlerFunc func(s *Session)
The HandlerFunc provides a convenient adapter type that allows for normal functions to handle 9P sessions.
type Logger ¶
type Logger interface {
Printf(string, ...interface{})
}
Types implementing the Logger interface can receive diagnostic information during a Server's operation. The Logger interface is implemented by *log.Logger.
type OwnerInfo ¶
The styx package will attempt to determine the ownership of a file by asking the host operating system, if it is a real file. If a given type implements the OwnerInfo interface, the styx package will use the methods therein to determine file ownership. Uid should return the user name of a file's owner. Gid should return the primary group of the file. Muid, if implemented, should return the name of the user who last modified the file. If Muid is not implemented, the styx package will always return the owner of the file for its Muid.
Usage of this interface is opportunistic; a type can implement all or some of the methods.
type Request ¶
type Request interface { // Context is used to implement cancellation and request timeouts. If // an operation is going to take a long time to complete, you can // allow for the client to cancel the request by receiving on the // channel returned by the Context's Done method. Context() context.Context // WithContext returns a copy of the request with a new Context. It // can be used with nested handlers to attach information, deadlines, // and cancellations to a request. WithContext(context.Context) Request // If a request is invalid, not allowed, or cannot be completed properly // for some other reason, its Rerror method should be used to respond // to it. Rerror(format string, args ...interface{}) // Path returns the Path of the file being operated on. Path() string // contains filtered or unexported methods }
A Request is a request by a client to perform an operation on a file or set of files. Types of requests may range from checking if a file exists (Twalk) to opening a file (Topen) to changing a file's name (Twstat).
type Server ¶
type Server struct { // Address to listen on, ":9pfs" if empty. Addr string // maximum wait before timing out write of the response. WriteTimeout time.Duration // maximum wait before closing an idle connection. IdleTimeout time.Duration // maximum size of a 9P message, DefaultMsize if unset. MaxSize int64 // optional TLS config, used by ListenAndServeTLS TLSConfig *tls.Config // Handler to invoke for each session Handler Handler // Auth is used to authenticate user sessions. If nil, // authentication is disabled. Auth AuthFunc // OpenAuth is used to open file to authentication agent OpenAuth AuthOpenFunc // If not nil, ErrorLog will be used to log unexpected // errors accepting or handling connections. TraceLog, // if not nil, will receive detailed protocol tracing // information. ErrorLog, TraceLog Logger }
A Server defines parameters for running a 9P server. The zero value of a Server is usable as a 9P server, and will use the defaults set by the styx package.
func (*Server) ListenAndServe ¶
ListenAndServe listens on the TCP network address srv.Addr and calls Serve to handle requests on incoming connections. If srv.Addr is blank, :564 is used.
func (*Server) ListenAndServeTLS ¶
ListenAndServeTLS listens on the TCP network address srv.Addr for incoming TLS connections.
type Session ¶
type Session struct { // User is the name of the user associated with the session. // When establishing a session, the client provides a username, This // may or may not be authenticated, depending on the Server in use. User string // Access is the name of the file tree requested by a client when // it establishes a session, in the "aname" field of its "Tattach" // request. When the EnableVHost option is used, if a client does // not specify one, this is set to the hostname the client used // to connect to the server, for non-TLS connections, and the SNI // provided by the client, for TLS connections. Access string // This tracks the number of fids pointing to this session in // conn.sessionFid. We need to know when all references are gone // so we can properly close any session channels. util.RefCount // contains filtered or unexported fields }
A Session is a sequence of related 9P messages from a single client. It begins when a client opens the root of a file tree, and ends when all of its files are closed. Sessions occur over a single connection and are associated with a single user and file tree. Over a single session, a user may perform multiple operations on multiple files. Multiple sessions may be multiplexed over a single connection.
func (*Session) Next ¶
Next waits for the next Request for a 9P session. The next request for the session can be accessed via the Request method if and only if Next returns true. Any previous messages retrieved for the session should not be modified or responded to after Next is called; if they have not been answered, the styx package will send default responses for them. The default response for a message can be found in the comments for each message type. Next returns false if the session has ended or there was an error receiving the next Request.
func (*Session) Request ¶
Request returns the last 9P message received by the Session. It is only valid until the next call to Next.
func (*Session) UpdateRequest ¶
When multiple Handlers are combined together using Stack, a handler may modify the incoming request using the UpdateRequest method. The current request will be overwritten with r, and reflected in calls to the Request method in he current and all downstream handlers.
type Tchmod ¶
A Tchmod message is sent by the client to change the permissions of an existing file. The client must have write access to the file's containing directory. Use the Rchmod method to indicate success.
The default response for a Tchmod request is an Rerror saying "permission denied"
func (Tchmod) Rchmod ¶
Rchmod, when called with a nil error, indicates that the permissions of the file were updated. Future stat requests should reflect the new file mode.
type Tchown ¶
type Tchown struct {
User, Group string
// These will only be set if using the 9P2000.u or 9P2000.L
// extensions, and will be -1 otherwise.
Uid, Gid int
// contains filtered or unexported fields
}
A Tchown message is sent by the client to change the user and group associated with a file. Use the Rchown method to indicate success.
The default response to a Tchown message is an Rerror message saying "permission denied".
func (Tchown) Rchown ¶
Rchown, when called with a nil error, indicates that file and group ownership attributes were updated for the given file. Future stat requests for the same file should reflect the changes.
type Tcreate ¶
type Tcreate struct { Name string // name of the file to create Mode os.FileMode // permissions and file type to create Flag int // flags to open the new file with // contains filtered or unexported fields }
A Tcreate message is sent when a client wants to create a new file and open it with the provided Mode. The Path method of a Tcreate message returns the absolute path of the containing directory. A user must have write permissions in the directory to create a file.
The default response to a Tcreate message is an Rerror message saying "permission denied".
func (Tcreate) NewPath ¶
NewPath joins the path for the Tcreate's containing directory with its Name field, returning the absolute path to the new file.
func (Tcreate) Rcreate ¶
Rcreate is used to respond to a succesful create request. With 9P, creating a file also opens the file for I/O. Once Rcreate returns, future read and write requests to the file handle will pass through rwc. The value rwc must meet the same criteria listed for the Ropen method of a Topen request.
type Topen ¶
type Topen struct { // The mode to open the file with. One of the flag constants // in the os package, such as O_RDWR, O_APPEND etc. Flag int // contains filtered or unexported fields }
A Topen message is sent when a client wants to open a file for I/O Use the Ropen method to provide the opened file.
The default response to a Topen message to send an Rerror message saying "permssion denied".
func (Topen) Path ¶
func (t Topen) Path() string
Path returns the absolute path of the file being operated on.
func (Topen) Rerror ¶
func (t Topen) Rerror(format string, args ...interface{})
Rerror sends an error to the client.
func (Topen) Ropen ¶
The Ropen method signals to the client that a file has succesfully been opened and is ready for I/O. After Ropen returns, future reads and writes to the opened file handle will pass through rwc.
The value rwc must implement some of the interfaces in the io package for reading and writing. If the type implements io.Seeker or io.ReaderAt and io.WriterAt, clients may read or write at arbitrary offsets within the file. Types that only implement Read or Write operations will return errors on writes and reads, respectively.
If rwc implements the Stat method of os.File, that will be used to answer Tstat requests. Otherwise, the styx package will assemble Rstat responses out of default values merged with any methods rwc provides from the os.FileInfo interface.
If a file does not implement any of the Read or Write interfaces in the io package, A generic error is returned to the client, and a message will be written to the server's ErrorLog.
type Tremove ¶
type Tremove struct {
// contains filtered or unexported fields
}
A Tremove message is sent when a client wants to delete a file from the server. The Rremove method should be called once the file has been succesfully deleted.
The default response to a Tremove message is an Rerror message saying "permission denied".
func (Tremove) Path ¶
func (t Tremove) Path() string
Path returns the absolute path of the file being operated on.
func (Tremove) Rerror ¶
func (t Tremove) Rerror(format string, args ...interface{})
Rerror sends an error to the client.
func (Tremove) Rremove ¶
Rremove signals to the client that a file has been succesfully removed. The file handle for the file is no longer valid, and may be re-used for other files. Whether or not any other file handles associated with the file continue to be usable for I/O is implementation-defined; many Unix file systems allow a process to continue writing to a file that has been "unlinked", so long as the process has an open file descriptor.
If err is non-nil, an Rerror message is sent to the client. Regardless, the file handle is no longer valid.
type Trename ¶
type Trename struct {
OldPath, NewPath string
// contains filtered or unexported fields
}
A Trename message is sent by the client to change the name of an existing file. Use the Rrename method to indicate success.
The default response for a Trename request is an Rerror message saying "permission denied"
func (Trename) Path ¶
The Path method of a Trename request returns the current path to the file, before a rename has taken place.
func (Trename) Rerror ¶
func (t Trename) Rerror(format string, args ...interface{})
Call Rerror to provide a descriptive error message explaining why a file attribute could not be updated.
type Tstat ¶
type Tstat struct {
// contains filtered or unexported fields
}
A Tstat message is sent when a client wants metadata about a file. A client should have read access to the file's containing directory. Call the Rstat method for a succesful request.
The default response for a Tstat message is an Rerror message saying "permission denied".
func (Tstat) Path ¶
func (t Tstat) Path() string
Path returns the absolute path of the file being operated on.
func (Tstat) Rerror ¶
func (t Tstat) Rerror(format string, args ...interface{})
Rerror sends an error to the client.
func (Tstat) Rstat ¶
Rstat responds to a succesful Tstat request. The styx package will translate the os.FileInfo value into the appropriate 9P structure. Rstat will attempt to resolve the names of the file's owner and group. If that cannot be done, an empty string is sent. If err is non-nil, and error is sent to the client instead.
type Tsync ¶
type Tsync struct {
// contains filtered or unexported fields
}
A Tsync request is made by the client to indicate that the client would like any changes made to the file to be flushed to durable storage. Use the Rsync method to indicate success.
The default response to a Tsync message is an Rerror message saying "not supported".
func (Tsync) Rerror ¶
func (t Tsync) Rerror(format string, args ...interface{})
Call Rerror to provide a descriptive error message explaining why a file attribute could not be updated.
type Ttruncate ¶
type Ttruncate struct { Size int64 // contains filtered or unexported fields }
A Ttruncate requests for the size of a file to be changed. Use the Rtruncate method to indicate success.
The default response to a Ttruncate message is an Rerror message saying "permission denied".
func (Ttruncate) Rerror ¶
func (t Ttruncate) Rerror(format string, args ...interface{})
Call Rerror to provide a descriptive error message explaining why a file attribute could not be updated.
type Tutimes ¶
A Tutimes message is sent by the client to change the modification time of a file. Use the Rutime method to indicate success.
The default response to a Tutimes message is an Rerror message saying "permission denied"
func (Tutimes) Rerror ¶
func (t Tutimes) Rerror(format string, args ...interface{})
Call Rerror to provide a descriptive error message explaining why a file attribute could not be updated.
type Twalk ¶
type Twalk struct {
// contains filtered or unexported fields
}
A client sends a Twalk message both to probe if a file exists, and to move a "cursor" within the filesystem hierarchy. In a traditional file system, a Twalk request is similar to using chdir to change the current directory. File servers are free to attach additional meaning to Twalk requests. For instance, a server may create directories on-demand as clients walk to them.
The 9P protocol allows for clients to walk multiple directories with a single 9P message. The styx package translates such requests into multiple Twalk values, providing the following guarantees:
- Path() will return a cleaned, absolute path
- Consecutive, related Twalk requests will differ by at most 1 path element.
The default response to a Twalk request is an Rerror message saying "No such file or directory".
func (Twalk) Path ¶
func (t Twalk) Path() string
Path returns the absolute path of the file being operated on.
func (Twalk) Rerror ¶
Rerror signals to the client that the file named by the Twalk's Path method does not exist.
func (Twalk) Rwalk ¶
Rwalk signals to the client that the file named by the Twalk's Path method exists and is of the given mode. The permission bits of mode are ignored, and only the file type bits, such as os.ModeDir, are sent to the client. If err is non-nil, an error response is sent to the client instead.
Notes ¶
Bugs ¶
cancellation of write requests is not yet implemented.
renaming a file with one fid will break Twalk requests that attempt to clone another fid pointing to the same file.
Source Files ¶
Directories ¶
Path | Synopsis |
---|---|
internal
|
|
netutil
Package netutil contains useful types for testing network services.
|
Package netutil contains useful types for testing network services. |
pool
Package pool manages pools of integer identifiers.
|
Package pool manages pools of integer identifiers. |
qidpool
Package qidpool manages pools of 9P Qids, 13-bit unique identifiers for files.
|
Package qidpool manages pools of 9P Qids, 13-bit unique identifiers for files. |
styxfile
Package styxfile provides helper routines and interfaces for serving 9P files from Go types.
|
Package styxfile provides helper routines and interfaces for serving 9P files from Go types. |
sys
Package sys contains non-portable routines.
|
Package sys contains non-portable routines. |
threadsafe
Package threadsafe implements data structures that are safe for use from multiple goroutines.
|
Package threadsafe implements data structures that are safe for use from multiple goroutines. |
tracing
Package tracing provides tracing of sent and received 9P messages.
|
Package tracing provides tracing of sent and received 9P messages. |
util
Package util contains internal routines used by the styx packages.
|
Package util contains internal routines used by the styx packages. |
Package styxauth provides authentication methods for 9P servers.
|
Package styxauth provides authentication methods for 9P servers. |
Package styxproto provides low-level routines for parsing and producing 9P2000 messages.
|
Package styxproto provides low-level routines for parsing and producing 9P2000 messages. |