package protocol import ( "io" "sync/atomic" "time" ) // File is the interface that files must implement. // This is the core abstraction for anything exposed via 9P. type File interface { // Stat returns the file's metadata Stat() Stat // Open prepares the file for reading/writing Open(mode uint8) error // Read reads up to len(p) bytes starting at offset Read(p []byte, offset int64) (n int, err error) // Write writes len(p) bytes starting at offset Write(p []byte, offset int64) (n int, err error) // Close releases any resources Close() error } // Dir is the interface that directories must implement type Dir interface { File // Children returns the directory's children Children() []File // Lookup finds a child by name Lookup(name string) (File, error) } // pathCounter generates unique path IDs for qids var pathCounter uint64 func NextPath() uint64 { return atomic.AddUint64(&pathCounter, 1) } // BaseFile provides a default implementation of common File methods type BaseFile struct { Name_ string Mode_ uint32 Uid_ string Gid_ string Qid_ Qid Mtime_ time.Time Length_ uint64 } // NewBaseFile creates a new base file func NewBaseFile(name string, mode uint32) *BaseFile { now := time.Now() qtype := QTFILE if mode&DMDIR != 0 { qtype = QTDIR } return &BaseFile{ Name_: name, Mode_: mode, Uid_: "llm", Gid_: "llm", Mtime_: now, Qid_: Qid{ Type: qtype, Version: 0, Path: NextPath(), }, } } func (f *BaseFile) Stat() Stat { return Stat{ Type: 0, Dev: 0, Qid: f.Qid_, Mode: f.Mode_, Atime: uint32(f.Mtime_.Unix()), Mtime: uint32(f.Mtime_.Unix()), Length: f.Length_, Name: f.Name_, Uid: f.Uid_, Gid: f.Gid_, Muid: f.Uid_, } } func (f *BaseFile) Open(mode uint8) error { return nil } func (f *BaseFile) Close() error { return nil } func (f *BaseFile) Read(p []byte, offset int64) (int, error) { return 0, io.EOF } func (f *BaseFile) Write(p []byte, offset int64) (int, error) { return 0, ErrPermission } // SetLength updates the file length func (f *BaseFile) SetLength(n uint64) { f.Length_ = n f.Mtime_ = time.Now() f.Qid_.Version++ } // StaticFile is a file with static content type StaticFile struct { *BaseFile Content []byte } // NewStaticFile creates a file with static content func NewStaticFile(name string, content []byte) *StaticFile { f := &StaticFile{ BaseFile: NewBaseFile(name, 0444), Content: content, } f.Length_ = uint64(len(content)) return f } func (f *StaticFile) Read(p []byte, offset int64) (int, error) { if offset >= int64(len(f.Content)) { return 0, io.EOF } n := copy(p, f.Content[offset:]) return n, nil } // StaticDir is a directory with static children type StaticDir struct { *BaseFile children map[string]File order []string // preserve order for listing } // NewStaticDir creates a new static directory func NewStaticDir(name string) *StaticDir { return &StaticDir{ BaseFile: NewBaseFile(name, DMDIR|0555), children: make(map[string]File), order: make([]string, 0), } } // AddChild adds a child to the directory func (d *StaticDir) AddChild(f File) { name := f.Stat().Name if _, exists := d.children[name]; !exists { d.order = append(d.order, name) } d.children[name] = f } func (d *StaticDir) Children() []File { result := make([]File, len(d.order)) for i, name := range d.order { result[i] = d.children[name] } return result } func (d *StaticDir) Lookup(name string) (File, error) { if f, ok := d.children[name]; ok { return f, nil } return nil, ErrNotFound } func (d *StaticDir) Read(p []byte, offset int64) (int, error) { // Directory read returns packed stat entries var buf []byte for _, f := range d.Children() { stat := f.Stat() entry := make([]byte, 256) n := stat.Encode(entry) buf = append(buf, entry[:n]...) } if offset >= int64(len(buf)) { return 0, io.EOF } n := copy(p, buf[offset:]) return n, nil } // DynamicFile is a file whose content is generated on read type DynamicFile struct { *BaseFile Generator func() []byte } // NewDynamicFile creates a file with dynamic content func NewDynamicFile(name string, generator func() []byte) *DynamicFile { return &DynamicFile{ BaseFile: NewBaseFile(name, 0444), Generator: generator, } } func (f *DynamicFile) Read(p []byte, offset int64) (int, error) { content := f.Generator() if offset >= int64(len(content)) { return 0, io.EOF } n := copy(p, content[offset:]) return n, nil } func (f *DynamicFile) Stat() Stat { s := f.BaseFile.Stat() s.Length = uint64(len(f.Generator())) return s } // Errors type Error string func (e Error) Error() string { return string(e) } const ( ErrNotFound Error = "file not found" ErrPermission Error = "permission denied" ErrNotDir Error = "not a directory" ErrIsDir Error = "is a directory" ErrBadFid Error = "bad fid" ErrFidInUse Error = "fid already in use" ErrBadOffset Error = "bad offset" )