virtfs: eliminate Binding type
Each() now returns []FsNodeDecl directly. The Binding type was a redundant subset of FsNodeDecl with a slightly different Remove signature. Dynamic entries are now expressed uniformly — Remove, Rename, Children, Aliases all live on FsNodeDecl like everything else. Also added: Alias() and RenameNode() options, DirNode accepts []FsNodeDecl for passing pre-built child slices.
This commit is contained in:
parent
3a875da9f6
commit
5a09e603cd
|
|
@ -64,15 +64,13 @@ func readSessionConnected(s *SessionNode) ([]byte, error) {
|
|||
return []byte("false\n"), nil
|
||||
}
|
||||
|
||||
func sessionCtlHandler(s *SessionNode, removeFn func(), renameFn func(string) error) func([]byte) ([]byte, error) {
|
||||
func sessionCtlHandler(s *SessionNode, removeFn func() error, renameFn func(string) error) func([]byte) ([]byte, error) {
|
||||
handlers := map[string]func([]string) ([]byte, error){
|
||||
"kill": func(_ []string) ([]byte, error) {
|
||||
removeFn()
|
||||
return []byte("ok\n"), nil
|
||||
return []byte("ok\n"), removeFn()
|
||||
},
|
||||
".": func(_ []string) ([]byte, error) {
|
||||
removeFn()
|
||||
return []byte("ok\n"), nil
|
||||
return []byte("ok\n"), removeFn()
|
||||
},
|
||||
"save": func(_ []string) ([]byte, error) {
|
||||
s.Save()
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ func buildTreeSpec(rs *RootState) virtfs.FsNodeDecl {
|
|||
virtfs.Read(func() ([]byte, error) { return readBypassPolicy(broker) }),
|
||||
virtfs.Write(func(data []byte) error { return writeBypassPolicy(broker, data) }),
|
||||
),
|
||||
virtfs.Each("{reqid}", func() ([]virtfs.Binding, error) {
|
||||
virtfs.Each("{reqid}", func() ([]virtfs.FsNodeDecl, error) {
|
||||
return adaptBypassBindings(broker)
|
||||
}),
|
||||
),
|
||||
|
|
@ -109,7 +109,7 @@ func buildTreeSpec(rs *RootState) virtfs.FsNodeDecl {
|
|||
virtfs.Doc("Session index"),
|
||||
virtfs.Read(readSessionIdx),
|
||||
),
|
||||
virtfs.Each("{sname}", func() ([]virtfs.Binding, error) {
|
||||
virtfs.Each("{sname}", func() ([]virtfs.FsNodeDecl, error) {
|
||||
return buildSessionBindings(rs, broker)
|
||||
}),
|
||||
),
|
||||
|
|
@ -117,18 +117,19 @@ func buildTreeSpec(rs *RootState) virtfs.FsNodeDecl {
|
|||
}
|
||||
|
||||
// buildSessionBindings produces bindings for each session.
|
||||
func buildSessionBindings(rs *RootState, broker *bypass.Broker) ([]virtfs.Binding, error) {
|
||||
func buildSessionBindings(rs *RootState, broker *bypass.Broker) ([]virtfs.FsNodeDecl, error) {
|
||||
sessions := session.Sessions()
|
||||
var out []virtfs.Binding
|
||||
var out []virtfs.FsNodeDecl
|
||||
|
||||
for name, sess := range sessions {
|
||||
node := rs.GetOrCreateNode(sess)
|
||||
s := node // capture
|
||||
n := name // capture
|
||||
|
||||
removeFn := func() {
|
||||
removeFn := func() error {
|
||||
session.Kill(n)
|
||||
rs.RemoveNode(n)
|
||||
return nil
|
||||
}
|
||||
renameFn := func(newName string) error {
|
||||
if err := session.Rename(n, newName); err != nil {
|
||||
|
|
@ -138,7 +139,7 @@ func buildSessionBindings(rs *RootState, broker *bypass.Broker) ([]virtfs.Bindin
|
|||
return nil
|
||||
}
|
||||
|
||||
out = append(out, virtfs.Binding{
|
||||
out = append(out, virtfs.FsNodeDecl{
|
||||
Name: sess.Name(),
|
||||
Aliases: []string{sess.ID},
|
||||
Remove: removeFn,
|
||||
|
|
@ -154,7 +155,7 @@ func buildSessionChildren(
|
|||
s *SessionNode,
|
||||
rs *RootState,
|
||||
broker *bypass.Broker,
|
||||
removeFn func(),
|
||||
removeFn func() error,
|
||||
renameFn func(string) error,
|
||||
) []virtfs.FsNodeDecl {
|
||||
return []virtfs.FsNodeDecl{
|
||||
|
|
@ -188,7 +189,7 @@ func buildSessionChildren(
|
|||
virtfs.FileNode("idx", 0444,
|
||||
virtfs.Read(func() ([]byte, error) { return readAgentIdx(s.Session) }),
|
||||
),
|
||||
virtfs.Each("{aname}", func() ([]virtfs.Binding, error) {
|
||||
virtfs.Each("{aname}", func() ([]virtfs.FsNodeDecl, error) {
|
||||
return buildAgentBindings(s, rs)
|
||||
}),
|
||||
),
|
||||
|
|
@ -196,9 +197,9 @@ func buildSessionChildren(
|
|||
}
|
||||
|
||||
// buildAgentBindings produces bindings for each agent in a session.
|
||||
func buildAgentBindings(s *SessionNode, rs *RootState) ([]virtfs.Binding, error) {
|
||||
func buildAgentBindings(s *SessionNode, rs *RootState) ([]virtfs.FsNodeDecl, error) {
|
||||
agents := s.Agents()
|
||||
var out []virtfs.Binding
|
||||
var out []virtfs.FsNodeDecl
|
||||
|
||||
for _, ag := range agents {
|
||||
a := ag
|
||||
|
|
@ -209,12 +210,13 @@ func buildAgentBindings(s *SessionNode, rs *RootState) ([]virtfs.Binding, error)
|
|||
wireAgentEvents(s.ID, a, al)
|
||||
}
|
||||
|
||||
out = append(out, virtfs.Binding{
|
||||
out = append(out, virtfs.FsNodeDecl{
|
||||
Name: a.Name(),
|
||||
Aliases: []string{a.ID()},
|
||||
Remove: func() {
|
||||
Remove: func() error {
|
||||
s.RemoveAgent(a.ID())
|
||||
session.PublishEvent("session."+s.ID+".agent."+a.ID()+".kill", "")
|
||||
return nil
|
||||
},
|
||||
Children: buildAgentChildren(a, al, s),
|
||||
})
|
||||
|
|
@ -283,7 +285,7 @@ func buildAgentChildren(a *agent.Agent, al *AgentLog, s *SessionNode) []virtfs.F
|
|||
virtfs.Read(func() ([]byte, error) { return readAgentID(a) }),
|
||||
),
|
||||
virtfs.DirNode("proc",
|
||||
virtfs.Each("{pid}", func() ([]virtfs.Binding, error) {
|
||||
virtfs.Each("{pid}", func() ([]virtfs.FsNodeDecl, error) {
|
||||
return adaptProcessBindings(s)
|
||||
}),
|
||||
),
|
||||
|
|
@ -291,15 +293,15 @@ func buildAgentChildren(a *agent.Agent, al *AgentLog, s *SessionNode) []virtfs.F
|
|||
}
|
||||
|
||||
// adaptBypassBindings returns bindings for pending bypass requests.
|
||||
func adaptBypassBindings(broker *bypass.Broker) ([]virtfs.Binding, error) {
|
||||
func adaptBypassBindings(broker *bypass.Broker) ([]virtfs.FsNodeDecl, error) {
|
||||
if broker == nil {
|
||||
return nil, errNoBypass
|
||||
}
|
||||
pending := broker.Pending()
|
||||
var out []virtfs.Binding
|
||||
var out []virtfs.FsNodeDecl
|
||||
for _, r := range pending {
|
||||
req := r
|
||||
out = append(out, virtfs.Binding{
|
||||
out = append(out, virtfs.FsNodeDecl{
|
||||
Name: req.ID,
|
||||
Children: []virtfs.FsNodeDecl{
|
||||
virtfs.FileNode("request", 0666,
|
||||
|
|
@ -317,7 +319,7 @@ func adaptBypassBindings(broker *bypass.Broker) ([]virtfs.Binding, error) {
|
|||
}
|
||||
|
||||
// adaptProcessBindings returns bindings for detached processes.
|
||||
func adaptProcessBindings(s *SessionNode) ([]virtfs.Binding, error) {
|
||||
func adaptProcessBindings(s *SessionNode) ([]virtfs.FsNodeDecl, error) {
|
||||
if s == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
|
@ -327,12 +329,12 @@ func adaptProcessBindings(s *SessionNode) ([]virtfs.Binding, error) {
|
|||
}
|
||||
defer conn.Close()
|
||||
raw := conn.ListDetachedRaw()
|
||||
var out []virtfs.Binding
|
||||
var out []virtfs.FsNodeDecl
|
||||
for _, r := range raw {
|
||||
if m, ok := r.(map[string]any); ok {
|
||||
if id, ok := m["pid"].(int); ok {
|
||||
pid := id
|
||||
out = append(out, virtfs.Binding{
|
||||
out = append(out, virtfs.FsNodeDecl{
|
||||
Name: strconv.Itoa(pid),
|
||||
Children: []virtfs.FsNodeDecl{
|
||||
virtfs.FileNode("out", 0444,
|
||||
|
|
|
|||
|
|
@ -58,16 +58,16 @@ func Spec(srv *Server) virtfs.FsNodeDecl {
|
|||
return []byte(result), nil
|
||||
}),
|
||||
),
|
||||
virtfs.Each("{pid}", func() ([]virtfs.Binding, error) {
|
||||
virtfs.Each("{pid}", func() ([]virtfs.FsNodeDecl, error) {
|
||||
pids := srv.Fs.ListProcs()
|
||||
var bindings []virtfs.Binding
|
||||
var out []virtfs.FsNodeDecl
|
||||
for _, pid := range pids {
|
||||
proc := srv.Fs.GetProc(pid)
|
||||
if proc == nil {
|
||||
continue
|
||||
}
|
||||
p := proc
|
||||
bindings = append(bindings, virtfs.Binding{
|
||||
out = append(out, virtfs.FsNodeDecl{
|
||||
Name: strconv.Itoa(pid),
|
||||
Children: []virtfs.FsNodeDecl{
|
||||
virtfs.FileNode("out", 0444,
|
||||
|
|
@ -98,7 +98,7 @@ func Spec(srv *Server) virtfs.FsNodeDecl {
|
|||
},
|
||||
})
|
||||
}
|
||||
return bindings, nil
|
||||
return out, nil
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -128,16 +128,15 @@ func listDir(d *FsNodeDecl, uid, gid string) ([]os.DirEntry, error) {
|
|||
for _, c := range d.Children {
|
||||
if isTemplateName(c.Name) {
|
||||
if c.Bindings != nil {
|
||||
bindings, err := c.Bindings()
|
||||
nodes, err := c.Bindings()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, b := range bindings {
|
||||
// If binding has children, it's a directory; otherwise check template for file handlers
|
||||
if len(b.Children) > 0 {
|
||||
entries = append(entries, dirEntry(b.Name, 0755))
|
||||
for _, n := range nodes {
|
||||
if len(n.Children) > 0 || n.Bindings != nil {
|
||||
entries = append(entries, dirEntry(n.Name, 0755))
|
||||
} else {
|
||||
entries = append(entries, fileEntry(b.Name, c.Mode))
|
||||
entries = append(entries, fileEntry(n.Name, c.Mode))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -321,31 +320,16 @@ func findChild(d *FsNodeDecl, name string) *FsNodeDecl {
|
|||
}
|
||||
}
|
||||
|
||||
// Dynamic children (Each): find matching binding by name/alias.
|
||||
// Dynamic children (Each): find matching entry by name/alias.
|
||||
for i := range d.Children {
|
||||
if isTemplateName(d.Children[i].Name) && d.Children[i].Bindings != nil {
|
||||
bindings, err := d.Children[i].Bindings()
|
||||
entries, err := d.Children[i].Bindings()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, b := range bindings {
|
||||
if b.Name == name || matchAlias(b.Aliases, name) {
|
||||
// Build a synthetic FsNodeDecl from the binding's children.
|
||||
node := FsNodeDecl{
|
||||
Name: b.Name,
|
||||
Aliases: b.Aliases,
|
||||
UID: b.UID,
|
||||
GID: b.GID,
|
||||
Children: b.Children,
|
||||
}
|
||||
if b.Remove != nil {
|
||||
rm := b.Remove
|
||||
node.Remove = func() error { rm(); return nil }
|
||||
}
|
||||
if b.Rename != nil {
|
||||
node.Rename = b.Rename
|
||||
}
|
||||
return &node
|
||||
for j := range entries {
|
||||
if entries[j].Name == name || matchAlias(entries[j].Aliases, name) {
|
||||
return &entries[j]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,11 +12,11 @@
|
|||
// FileNode("version", 0444, Read(func() ([]byte, error) {
|
||||
// return []byte("1.0\n"), nil
|
||||
// })),
|
||||
// Each("{user}", func() ([]Binding, error) {
|
||||
// var out []Binding
|
||||
// Each("{user}", func() ([]FsNodeDecl, error) {
|
||||
// var out []FsNodeDecl
|
||||
// for _, u := range users {
|
||||
// user := u
|
||||
// out = append(out, Binding{
|
||||
// out = append(out, FsNodeDecl{
|
||||
// Name: user.Name,
|
||||
// Children: []FsNodeDecl{
|
||||
// FileNode("name", 0444, Read(func() ([]byte, error) {
|
||||
|
|
@ -52,8 +52,8 @@ type FsNodeDecl struct {
|
|||
Stat func() os.FileInfo
|
||||
|
||||
// Twalk — children (implies directory; at most one set).
|
||||
Children []FsNodeDecl // static, known at compile time
|
||||
Bindings func() ([]Binding, error) // Each: dynamic children
|
||||
Children []FsNodeDecl // static, known at compile time
|
||||
Bindings func() ([]FsNodeDecl, error) // Each: dynamic children
|
||||
|
||||
// Tread (non-blocking) + Twrite.
|
||||
Read func() ([]byte, error)
|
||||
|
|
@ -74,17 +74,6 @@ type FsNodeDecl struct {
|
|||
// NodeOption configures a FsNodeDecl at construction time.
|
||||
type NodeOption func(*FsNodeDecl)
|
||||
|
||||
// Binding provides identity and pre-bound children for one iteration of Each.
|
||||
type Binding struct {
|
||||
Name string
|
||||
Aliases []string
|
||||
UID string
|
||||
GID string
|
||||
Children []FsNodeDecl // fully-bound subtree (handlers are closures)
|
||||
Remove func()
|
||||
Rename func(string) error
|
||||
}
|
||||
|
||||
// ── EDSL constructors ────────────────────────────────────────────
|
||||
|
||||
// DirNode declares a static directory node.
|
||||
|
|
@ -94,6 +83,8 @@ func DirNode(name string, args ...any) FsNodeDecl {
|
|||
switch v := a.(type) {
|
||||
case FsNodeDecl:
|
||||
d.Children = append(d.Children, v)
|
||||
case []FsNodeDecl:
|
||||
d.Children = append(d.Children, v...)
|
||||
case NodeOption:
|
||||
v(&d)
|
||||
}
|
||||
|
|
@ -102,9 +93,8 @@ func DirNode(name string, args ...any) FsNodeDecl {
|
|||
}
|
||||
|
||||
// Each declares a directory whose children are produced dynamically.
|
||||
// The bindings function returns pre-bound children for each instance.
|
||||
func Each(name string, bindings func() ([]Binding, error), opts ...NodeOption) FsNodeDecl {
|
||||
d := FsNodeDecl{Name: name, Bindings: bindings}
|
||||
func Each(name string, list func() ([]FsNodeDecl, error), opts ...NodeOption) FsNodeDecl {
|
||||
d := FsNodeDecl{Name: name, Bindings: list}
|
||||
for _, o := range opts {
|
||||
o(&d)
|
||||
}
|
||||
|
|
@ -152,6 +142,16 @@ func RemoveNode(fn func() error) NodeOption {
|
|||
return func(d *FsNodeDecl) { d.Remove = fn }
|
||||
}
|
||||
|
||||
// RenameNode sets the rename handler for a node.
|
||||
func RenameNode(fn func(string) error) NodeOption {
|
||||
return func(d *FsNodeDecl) { d.Rename = fn }
|
||||
}
|
||||
|
||||
// Alias adds alternate lookup names for this node.
|
||||
func Alias(names ...string) NodeOption {
|
||||
return func(d *FsNodeDecl) { d.Aliases = append(d.Aliases, names...) }
|
||||
}
|
||||
|
||||
// StatOverride sets a custom stat handler.
|
||||
func StatOverride(fn func() os.FileInfo) NodeOption {
|
||||
return func(d *FsNodeDecl) { d.Stat = fn }
|
||||
|
|
|
|||
|
|
@ -183,10 +183,10 @@ func TestFileConfigModes(t *testing.T) {
|
|||
func TestDeleteDir(t *testing.T) {
|
||||
var deleted bool
|
||||
spec := DirNode("/",
|
||||
Each("{id}", func() ([]Binding, error) {
|
||||
return []Binding{{
|
||||
Each("{id}", func() ([]FsNodeDecl, error) {
|
||||
return []FsNodeDecl{{
|
||||
Name: "item",
|
||||
Remove: func() { deleted = true },
|
||||
Remove: func() error { deleted = true; return nil },
|
||||
Children: []FsNodeDecl{
|
||||
FileNode("data", 0444),
|
||||
},
|
||||
|
|
@ -215,8 +215,8 @@ func TestDeleteDirNotFound(t *testing.T) {
|
|||
func TestRenameDir(t *testing.T) {
|
||||
var newName string
|
||||
spec := DirNode("/",
|
||||
Each("{id}", func() ([]Binding, error) {
|
||||
return []Binding{{
|
||||
Each("{id}", func() ([]FsNodeDecl, error) {
|
||||
return []FsNodeDecl{{
|
||||
Name: "item",
|
||||
Rename: func(n string) error { newName = n; return nil },
|
||||
Children: []FsNodeDecl{
|
||||
|
|
@ -462,11 +462,11 @@ func TestEachClosureCapture(t *testing.T) {
|
|||
items := []Item{{"foo", "hello"}, {"bar", "world"}}
|
||||
|
||||
spec := DirNode("/",
|
||||
Each("{item}", func() ([]Binding, error) {
|
||||
var out []Binding
|
||||
Each("{item}", func() ([]FsNodeDecl, error) {
|
||||
var out []FsNodeDecl
|
||||
for _, item := range items {
|
||||
it := item
|
||||
out = append(out, Binding{
|
||||
out = append(out, FsNodeDecl{
|
||||
Name: it.Name,
|
||||
Children: []FsNodeDecl{
|
||||
FileNode("value", 0444, Read(func() ([]byte, error) {
|
||||
|
|
@ -510,8 +510,8 @@ func TestEachClosureCapture(t *testing.T) {
|
|||
|
||||
func TestEachDirListing(t *testing.T) {
|
||||
spec := DirNode("/",
|
||||
Each("{item}", func() ([]Binding, error) {
|
||||
return []Binding{
|
||||
Each("{item}", func() ([]FsNodeDecl, error) {
|
||||
return []FsNodeDecl{
|
||||
{Name: "alpha", Children: []FsNodeDecl{FileNode("x", 0444)}},
|
||||
{Name: "beta", Children: []FsNodeDecl{FileNode("x", 0444)}},
|
||||
}, nil
|
||||
|
|
@ -533,8 +533,8 @@ func TestEachDirListing(t *testing.T) {
|
|||
|
||||
func TestEachWithAliases(t *testing.T) {
|
||||
spec := DirNode("/",
|
||||
Each("{item}", func() ([]Binding, error) {
|
||||
return []Binding{{
|
||||
Each("{item}", func() ([]FsNodeDecl, error) {
|
||||
return []FsNodeDecl{{
|
||||
Name: "friendly",
|
||||
Aliases: []string{"uuid-123"},
|
||||
Children: []FsNodeDecl{
|
||||
|
|
@ -561,8 +561,8 @@ func TestEachWithAliases(t *testing.T) {
|
|||
|
||||
func TestStatDir(t *testing.T) {
|
||||
spec := DirNode("/",
|
||||
Each("{item}", func() ([]Binding, error) {
|
||||
return []Binding{{
|
||||
Each("{item}", func() ([]FsNodeDecl, error) {
|
||||
return []FsNodeDecl{{
|
||||
Name: "sub",
|
||||
Children: []FsNodeDecl{FileNode("file", 0444)},
|
||||
}}, nil
|
||||
|
|
|
|||
Loading…
Reference in New Issue