Documentation
¶
Overview ¶
Package fs is an package that implements a hierarchical filesystem as a struct, FS. An FS contains a hierarchy of Dirs and Files. The package also contains other types and functions useful for building 9p filesystems.
Constructing simple filesystems is easy. For example, creating a filesystem with a single file with static contents "Hello, World!" can be done as follows:
staticFS := fs.NewFS("glenda", "glenda", 0555) staticFS.Root.AddChild(fs.NewStaticFile( staticFS.NewStat("name.of.file", "owner.name", "group.name", 0444), []byte("Hello, World!\n"), ))
The filesystem can be served with one of the functions from github.com/knusbaum/go9p:
go9p.PostSrv("example", staticFS.Server())
There are more examples in this package and in github.com/knusbaum/go9p/examples
Index ¶
- func FullPath(f FSNode) string
- func NewFS(rootUser, rootGroup string, rootPerms uint32, opts ...Option) (*FS, *StaticDir)
- func PlainAuth(userpass map[string]string) func(io.ReadWriter) (string, error)
- func Plan9Auth(s io.ReadWriter) (string, error)
- func RMFile(fs *FS, f FSNode) error
- type BaseFile
- func (f *BaseFile) Close(fid uint64) error
- func (f *BaseFile) Open(fid uint64, omode proto.Mode) error
- func (f *BaseFile) Parent() Dir
- func (f *BaseFile) Read(fid uint64, offset uint64, count uint64) ([]byte, error)
- func (f *BaseFile) SetParent(p Dir)
- func (f *BaseFile) Stat() proto.Stat
- func (f *BaseFile) Write(fid uint64, offset uint64, data []byte) (uint32, error)
- func (f *BaseFile) WriteStat(s *proto.Stat) error
- type BiDiStream
- type BiDiStreamFile
- func (f *BiDiStreamFile) Close(fid uint64) error
- func (f *BiDiStreamFile) Open(fid uint64, omode proto.Mode) error
- func (f *BiDiStreamFile) Read(fid uint64, offset uint64, count uint64) ([]byte, error)
- func (f *BiDiStreamFile) Stat() proto.Stat
- func (f *BiDiStreamFile) Write(fid uint64, offset uint64, data []byte) (uint32, error)
- type BlockingStream
- func (s *BlockingStream) AddReadWriter() StreamReadWriter
- func (s *BlockingStream) AddReader() StreamReader
- func (s *BlockingStream) Close() error
- func (s *BlockingStream) Read(p []byte) (n int, err error)
- func (s *BlockingStream) RemoveReader(r StreamReader)
- func (s *BlockingStream) Write(p []byte) (n int, err error)
- type Dir
- type DroppingStream
- func (s *DroppingStream) AddReadWriter() StreamReadWriter
- func (s *DroppingStream) AddReader() StreamReader
- func (s *DroppingStream) Close() error
- func (s *DroppingStream) Read(p []byte) (n int, err error)
- func (s *DroppingStream) RemoveReader(r StreamReader)
- func (s *DroppingStream) Write(p []byte) (n int, err error)
- type DynamicFile
- type FS
- type FSNode
- type File
- type ListenFile
- type ListenFileListener
- type ModDir
- type Option
- func IgnorePermissions() Option
- func WithAuth(authFunc func(s io.ReadWriter) (string, error)) Option
- func WithCreateDir(...) Option
- func WithCreateFile(...) Option
- func WithRemoveFile(f func(fs *FS, f FSNode) error) Option
- func WithWalkFailHandler(f func(fs *FS, parent Dir, name string) (FSNode, error)) Option
- type PipeFile
- type SavedStream
- type SkippingStream
- func (s *SkippingStream) AddReadWriter() StreamReadWriter
- func (s *SkippingStream) AddReader() StreamReader
- func (s *SkippingStream) Close() error
- func (s *SkippingStream) Read(p []byte) (n int, err error)
- func (s *SkippingStream) RemoveReader(r StreamReader)
- func (s *SkippingStream) Write(p []byte) (n int, err error)
- type StaticDir
- func (d *StaticDir) AddChild(n FSNode) error
- func (d *StaticDir) Children() map[string]FSNode
- func (d *StaticDir) DeleteChild(name string) error
- func (d *StaticDir) Parent() Dir
- func (d *StaticDir) SetParent(p Dir)
- func (d *StaticDir) Stat() proto.Stat
- func (d *StaticDir) WriteStat(s *proto.Stat) error
- type StaticFile
- type Stream
- type StreamFile
- func (f *StreamFile) Close(fid uint64) error
- func (f *StreamFile) Open(fid uint64, omode proto.Mode) error
- func (f *StreamFile) Read(fid uint64, offset uint64, count uint64) ([]byte, error)
- func (f *StreamFile) Stat() proto.Stat
- func (f *StreamFile) Write(fid uint64, offset uint64, data []byte) (uint32, error)
- type StreamReadWriter
- type StreamReader
- type WrappedFile
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func FullPath ¶
FullPath is a helper function that assembles the names of all the parent nodes of f into a full path string.
func NewFS ¶
NewFS constructs and returns an *FS. Options may be passed to do things like setting the various hook functions.
func RMFile ¶
RMFile is a function intended to be used with the WithRemoveFile Option. RMFile simply enables the deletion of files and directories on the FS subject to usual permissions checks.
Example ¶
package main import ( "github.com/knusbaum/go9p" "github.com/knusbaum/go9p/fs" ) func main() { staticFS, root := fs.NewFS("glenda", "glenda", 0555, // This Option will cause the FS to call fs.RMFile when a user with // permission attempts to remove a file. fs.RMFile simply deletes // the file with no further checking. fs.WithRemoveFile(fs.RMFile), ) root.AddChild(fs.NewStaticFile( // Note the permissions 0544. Someone authenticated as the user "glenda" // will have the permission to remove the file. staticFS.NewStat("hello", "owner.name", "group.name", 0544), []byte("Hello, World!\n"), )) go9p.Serve("localhost:9999", staticFS.Server()) }
Output:
Types ¶
type BaseFile ¶
BaseFile provides a simple File implementation that other implementations can base themselves off of. On its own it's not too useful. Stat and WriteStat work as expected, as do Parent and SetParent. Open always succeeds. Read always returns a zero-byte slice, Write always fails, and Close always succeeds.
Note, BaseFile is most useful when you want to create a custom File type rather than creating a single special file. Most of the time, you may want to use WrappedFile for custom behavior.
Example ¶
ExampleBaseFile demonstrates how to use a BaseFile to create a File type with custom behavior. In this case, we override the Read() function to return random bytes.
package main import ( "math/rand" "github.com/knusbaum/go9p" "github.com/knusbaum/go9p/fs" ) type randomFile struct { fs.BaseFile } func (f *randomFile) Read(fid uint32, offset, count uint64) ([]byte, error) { bs := make([]byte, count) rand.Read(bs) return bs, nil } func main() { // type randomFile struct { // fs.BaseFile // } // // func (f *randomFile) Read(fid uint32, offset, count uint64) ([]byte, error) { // bs := make([]byte, count) // rand.Read(bs) // return bs, nil // } randomFS, root := fs.NewFS("glenda", "glenda", 0555) root.AddChild(&randomFile{ *fs.NewBaseFile(randomFS.NewStat("random", "owner.name", "group.name", 0444)), }) go9p.Serve("localhost:9999", randomFS.Server()) }
Output:
func NewBaseFile ¶
type BiDiStream ¶
type BiDiStream interface { Stream AddReadWriter() StreamReadWriter Read(p []byte) (n int, err error) }
A BiDiStream is a bi-directional stream. It adds two methods to the Stream interface, AddReadWriter and Read. BiDiStream allows Read()s of data written by StreamReadWriters. Although there is no way to determine which StreamReadWriter wrote the bytes read by Read(), it is guaranteed that a Write()s done by StreamReadWriters are delivered as complete chunks. That is, all bytes from a single StreamReadWriter.Write() call will be read by Read() before bytes from another StreamReader.Write() will be read.
type BiDiStreamFile ¶
type BiDiStreamFile struct { *BaseFile // contains filtered or unexported fields }
BiDiStreamFile implements a two-way (full-duplex) stream. Writes to the stream will be read by clients who open the file. Writes by clients will be received by calls to Read() on the stream.
func (*BiDiStreamFile) Close ¶
func (f *BiDiStreamFile) Close(fid uint64) error
func (*BiDiStreamFile) Stat ¶
func (f *BiDiStreamFile) Stat() proto.Stat
type BlockingStream ¶
type BlockingStream struct {
// contains filtered or unexported fields
}
A BlockingStream will ensure all data is written to active StreamReaders, or block if the readers are unable to receive the data. A certain number of bytes of data is buffered (see NewBlockingStream). If an unresponsive reader is removed, Write will be able to continue.
func NewBlockingStream ¶
func NewBlockingStream(buffer int) *BlockingStream
func (*BlockingStream) AddReadWriter ¶
func (s *BlockingStream) AddReadWriter() StreamReadWriter
func (*BlockingStream) AddReader ¶
func (s *BlockingStream) AddReader() StreamReader
func (*BlockingStream) RemoveReader ¶
func (s *BlockingStream) RemoveReader(r StreamReader)
type DroppingStream ¶
type DroppingStream struct {
// contains filtered or unexported fields
}
DroppingStream is a stream which will disconnect readers who aren't able to keep up with the writer. The writer will slow down when writing (pausing for 50ms) to allow slower clients to catch up, but those that cannot keep up will be closed.
func NewDroppingStream ¶
func NewDroppingStream(buffer int) *DroppingStream
func (*DroppingStream) AddReadWriter ¶
func (s *DroppingStream) AddReadWriter() StreamReadWriter
func (*DroppingStream) AddReader ¶
func (s *DroppingStream) AddReader() StreamReader
func (*DroppingStream) RemoveReader ¶
func (s *DroppingStream) RemoveReader(r StreamReader)
type DynamicFile ¶
type DynamicFile struct { BaseFile // contains filtered or unexported fields }
DynamicFile is a File implementation that will serve dynamic content. Every time a client opens the DynamicFile, content is generated for the opening fid with the function genContent, which is passed to NewDynamicFile. Subsequent reads on the fid will return byte ranges from that content generated when the fid was opened. Closing the fid releases the content.
Example ¶
package main import ( "time" "github.com/knusbaum/go9p" "github.com/knusbaum/go9p/fs" ) func main() { dynamicFS, root := fs.NewFS("glenda", "glenda", 0555) root.AddChild(fs.NewDynamicFile( dynamicFS.NewStat("name.of.file", "owner.name", "group.name", 0444), func() []byte { return []byte(time.Now().String() + "\n") }, )) go9p.Serve("localhost:9999", dynamicFS.Server()) }
Output:
func NewDynamicFile ¶
func NewDynamicFile(s *proto.Stat, genContent func() []byte) *DynamicFile
NewDynamicFile creates a new DynamicFile that will use getContent to generate the file's content for each fid that opens it.
func (*DynamicFile) Close ¶
func (f *DynamicFile) Close(fid uint64) error
type FS ¶
type FS struct { Root Dir CreateFile func(fs *FS, parent Dir, user, name string, perm uint32, mode uint8) (File, error) CreateDir func(fs *FS, parent Dir, user, name string, perm uint32, mode uint8) (Dir, error) WalkFail func(fs *FS, parent Dir, name string) (FSNode, error) RemoveFile func(fs *FS, f FSNode) error sync.RWMutex // contains filtered or unexported fields }
The FS structure represents a hierarchical filesystem tree. It must contain a Root Dir, but all of the function members are optional. If provided, CreateFile is called when a client attempts to create a file. Similarly, CreateDir is called when a client attempts to create a directory. WalkFail's usefulness is dubious, but is called when a client walks to a path that does not exist in the fs. It can be used to create files on the fly. CreateFile returns a File, CreateDir returns a Dir, and WalkFail should return either a File or a Dir. All three can return an error, in which case the Error() will be returned to the client in a proto.TError. nil, nil is also an appropriate return pair, in which case a proto.TError with a generic message will be returned to the client.
FS is a tree structure. Every internal node should be a Dir - only instances of Dir can have children. Instances of File can only be leaves of the tree.
func (*FS) NewQid ¶
NewQid generates a new, unique proto.Qid for use in a new file. Each file in the FS should have a unique proto.Qid. statMode should come from the file's Stat().Mode
type FSNode ¶
type FSNode interface { Stat() proto.Stat WriteStat(s *proto.Stat) error SetParent(d Dir) Parent() Dir }
FSNode represents a node in a FS tree. It should track its Parent, which should be the Dir that the FSNode belongs to. SetParent should not be called directly. Instead, use the AddChild and DeleteChild functions on a Dir to add, remove, and move FSNodes around a filesystem.
type File ¶
type File interface { FSNode Open(fid uint64, omode proto.Mode) error Read(fid uint64, offset uint64, count uint64) ([]byte, error) Write(fid uint64, offset uint64, data []byte) (uint32, error) Close(fid uint64) error }
File represents a leaf node in the FS tree. It must implement Open, Read, Write, and Close methods. fid is a unique number representing an open file descriptor to the file. For each fid, Open will be called before Read, Write, or Close, and should open a file in the given omode, or return an error. If Open returns an error, fid is not valid. Following a successful Open, a given fid represents the same file descriptor until Close is called. Read requests count bytes at offset in the file, for file descriptor fid. Similarly, Write should write data into the file from file descriptor fid at offset. Both Read and Write may return error, which will be sent back to the client in a proto.TError message.
See StaticFile for a simple File implementation.
func CreateStaticFile ¶
CreateStaticFile is a function meant to be passed to WithCreateFile. It will add an empty StaticFile to the FS whenever a client attempts to create a file.
func NewStreamFile ¶
NewStreamFile creates a file that serves a stream to clients. If the Stream s implements the BiDiStream protocol, a BiDiStreamFile is returned. Otherwise a StreamFile is returned. Each Open() creates a new Reader or ReadWriter on the stream, and subsequent reads and writes on that fid operate on that Reader or ReadWriter. A Close() on a fid will close the Reader/ReadWriter.
type ListenFile ¶
type ListenFile struct { BaseFile // contains filtered or unexported fields }
ListenFile implements a net.Listener as a 9p File. The file is a stream, so offsets for Read and Write are ignored.
func NewListenFile ¶
func NewListenFile(s *proto.Stat) *ListenFile
func (*ListenFile) Close ¶
func (f *ListenFile) Close(fid uint64) error
type ListenFileListener ¶
type ListenFileListener ListenFile
func (*ListenFileListener) Accept ¶
func (l *ListenFileListener) Accept() (net.Conn, error)
Accept waits for and returns the next connection to the listener.
func (*ListenFileListener) Addr ¶
func (l *ListenFileListener) Addr() net.Addr
Addr returns the listener's network address.
func (*ListenFileListener) Close ¶
func (l *ListenFileListener) Close() error
Close closes the listener. Any blocked Accept operations will be unblocked and return errors.
type Option ¶
type Option func(*FS)
Options are used to configure an FS with NewFS
func IgnorePermissions ¶
func IgnorePermissions() Option
IgnorePermissions configures the server to not enforce user/group permissions bits. This is useful, for instance, when permissions need to be enforced at a higher level, or by an underlying file system that is being exported by the server.
func WithAuth ¶
func WithAuth(authFunc func(s io.ReadWriter) (string, error)) Option
WithAuth configures the server to require authentication. Authentication is performed using the standard plan9 or plan9port tools. A factotum must be running in the same namespace as this server in order to authenticate users. Please see http://man.cat-v.org/9front/4/factotum for more information.
func WithCreateDir ¶
func WithCreateDir(f func(fs *FS, parent Dir, user, name string, perm uint32, mode uint8) (Dir, error)) Option
WithCreateDir configures a function to be called when a client attempts to create a directory on the FS. The function f, if successful, should return a Dir, which it should add to the parent Dir. If a file should not be created, f can return an error which will be sent to the client. Basic permissions checking is done by the FS before calling f, but any other checking can be done by f.
func WithCreateFile ¶
func WithCreateFile(f func(fs *FS, parent Dir, user, name string, perm uint32, mode uint8) (File, error)) Option
WithCreateFile configures a function to be called when a client attempts to create a file on the FS. The function f, if successful, should return a File, which it should add to the parent Dir. If a file should not be created, f can return an error which will be sent to the client. Basic permissions checking is done by the FS before calling f, but any other checking can be done by f.
func WithRemoveFile ¶
WithRemoveFile configures a function to be called when a client attempts to remove a file or directory from the FS. The function f, if successful, should remove the FSNode from its parent Dir, and return nil. If f does not choose to remove the FSNode, it should return an error, which will be sent to the client.
func WithWalkFailHandler ¶
WithWalkFailHandler configures a function to be called when a client attempts to walk a path on the FS that does not exist. The function f may decide to create a File or Directory on the fly, which should be returned. If f chooses not to create an FSNode to satisfy the walk, an error may be returned which will be sent to the client.
type PipeFile ¶
type PipeFile struct { *BaseFile // contains filtered or unexported fields }
PipeFile creates a new full-duplex stream for each call to Open() The stream will be passed as a BiDiStream to the handler function passed to NewPipeFile, which will be run in a new goroutine. When that function returns, the stream will be closed.
func NewPipeFile ¶
func NewPipeFile(stat *proto.Stat, handler func(s BiDiStream)) *PipeFile
NewPipeFile creates a new PipeFile that will create a new bi-directional stream for every Open(), starting a goroutine calling handler on the stream. handler should loop to read the stream. When handler returns, the stream will be closed.
type SavedStream ¶
A SavedStream is a file-backed stream. Readers receive the full contents of the stream starting at the beginning and receive any new Writes.
func NewSavedStream ¶
func NewSavedStream(path string) (*SavedStream, error)
func (*SavedStream) AddReader ¶
func (s *SavedStream) AddReader() StreamReader
func (*SavedStream) Close ¶
func (s *SavedStream) Close() error
func (*SavedStream) RemoveReader ¶
func (s *SavedStream) RemoveReader(r StreamReader)
type SkippingStream ¶
type SkippingStream struct {
// contains filtered or unexported fields
}
A SkippingStream Sends all Writes() to all StreamReaders, skipping those that are not able to keep up. Once a StreamReader starts reading again, it will get new Writes, having skipped some of the data. This may be good for things like event streams, audio, etc.
func NewSkippingStream ¶
func NewSkippingStream(buffer int) *SkippingStream
func (*SkippingStream) AddReadWriter ¶
func (s *SkippingStream) AddReadWriter() StreamReadWriter
func (*SkippingStream) AddReader ¶
func (s *SkippingStream) AddReader() StreamReader
func (*SkippingStream) RemoveReader ¶
func (s *SkippingStream) RemoveReader(r StreamReader)
type StaticDir ¶
StaticDir is a Dir that simply keeps track of a set of child Files.
func NewStaticDir ¶
func (*StaticDir) DeleteChild ¶
type StaticFile ¶
StaticFile implements File. It is a very simple implementation that allows the reading and writing of a byte slice that every client sees. Writes modify the content and reads serve the content.
Example ¶
package main import ( "github.com/knusbaum/go9p" "github.com/knusbaum/go9p/fs" ) func main() { staticFS, root := fs.NewFS("glenda", "glenda", 0555) root.AddChild(fs.NewStaticFile( staticFS.NewStat("name.of.file", "owner.name", "group.name", 0444), []byte("Hello, World!\n"), )) go9p.Serve("localhost:9999", staticFS.Server()) }
Output:
func NewStaticFile ¶
func NewStaticFile(s *proto.Stat, data []byte) *StaticFile
NewStaticFile returns a StaticFile that contains the byte slice data.
func (*StaticFile) Stat ¶
func (f *StaticFile) Stat() proto.Stat
type Stream ¶
type Stream interface { AddReader() StreamReader RemoveReader(r StreamReader) Write(p []byte) (n int, err error) Close() error // contains filtered or unexported methods }
A Stream is a fan-out pipe of data. Stream implements io.Writer, and can have multiple readers associated with it, created with AddReader. How and whether data written with Write() is delivered to every reader is up to the implementation.
For instance, when calling stream.Write([]byte("abc")) on a stream with 3 readers, it is equally valid for a stream to send "abc" to one and nothing to the others, or send "a" to one, "b" to the second, and "c" to the third, or to send "abc" to all three. See individual Stream implementations for their specific behavior.
type StreamFile ¶
type StreamFile struct { *BaseFile // contains filtered or unexported fields }
StreamFile implements a one-way stream. Writes by clients return errors.
func (*StreamFile) Close ¶
func (f *StreamFile) Close(fid uint64) error
func (*StreamFile) Stat ¶
func (f *StreamFile) Stat() proto.Stat
type StreamReadWriter ¶
type StreamReadWriter interface { StreamReader Write(p []byte) (n int, err error) }
A StreamReadWriter is just like a StreamReader, but allows the Reader to send data back to the Stream.
type StreamReader ¶
A StreamReader reads from a Stream. See Stream.AddReader.
type WrappedFile ¶
type WrappedFile struct { File OpenF func(fid uint64, omode proto.Mode) error ReadF func(fid uint64, offset uint64, count uint64) ([]byte, error) WriteF func(fid uint64, offset uint64, data []byte) (uint32, error) CloseF func(fid uint64) error }
WrappedFile takes an existing File and adds optional hooks for the Open, Read, Write, and Close functions. OpenF, ReadF, WriteF, and CloseF, if set, are called rather than the File's Open, Read, Write, and Close functions.
Example ¶
ExampleWrappedFile demonstrates how to use a WrappedFile to create a File instance with custom behavior. In this case, we set ReadF to return random bytes.
package main import ( "math/rand" "github.com/knusbaum/go9p" "github.com/knusbaum/go9p/fs" ) func main() { randomFS, root := fs.NewFS("glenda", "glenda", 0555) root.AddChild(fs.WrappedFile{ File: fs.NewBaseFile(randomFS.NewStat("name.of.file", "owner.name", "group.name", 0444)), ReadF: func(fid uint64, offset uint64, count uint64) ([]byte, error) { bs := make([]byte, count) rand.Read(bs) return bs, nil }, }) go9p.Serve("localhost:9999", randomFS.Server()) }
Output:
func (*WrappedFile) Close ¶
func (f *WrappedFile) Close(fid uint64) error