diff --git a/internal/p9/server.go b/internal/p9/server.go index dfe6ab1..0f8f133 100644 --- a/internal/p9/server.go +++ b/internal/p9/server.go @@ -267,6 +267,17 @@ func (s *Server) handle(cs *connState, fc *plan9.Fcall) *plan9.Fcall { } } +// isSessionStoreFile reports whether path is a fixed file directly under /s/ +// (i.e. /s/ where name is in sessionStoreFiles). +func isSessionStoreFile(path string) bool { + name, ok := strings.CutPrefix(path, "/s/") + if !ok || strings.Contains(name, "/") { + return false + } + _, ok = sessionStoreFiles[name] + return ok +} + func errFcall(fc *plan9.Fcall, msg string) *plan9.Fcall { return &plan9.Fcall{Type: plan9.Rerror, Tag: fc.Tag, Ename: msg} } @@ -572,8 +583,8 @@ func (s *Server) read(cs *connState, fc *plan9.Fcall) *plan9.Fcall { } } - // s/new, s/idx, s/kill, and s/ls are served from the session store. - if path == "/s/new" || path == "/s/idx" || path == "/s/ls" || path == "/s/kill" { + // Fixed files directly under /s/ are served from the session store. + if isSessionStoreFile(path) { plog.Debug("Tread path=%q offset=%d count=%d", path, fc.Offset, fc.Count) content, err := s.sessionStore.Get(pathBase(path)) if err != nil { @@ -1451,12 +1462,10 @@ func (s *Server) makeStat(path string) plan9.Dir { case "backend", "agent", "model", "cwd": mode = 0666 default: - if path == "/backends" || path == "/help" || path == "/s/idx" { + if path == "/backends" || path == "/help" { mode = 0444 - } else if path == "/s/ls" || path == "/s/kill" { - mode = 0555 - } else if path == "/s/new" { - mode = 0666 + } else if isSessionStoreFile(path) { + mode = plan9.Perm(sessionStoreFiles[base]) } else if strings.HasPrefix(path, "/a/") { mode = 0666 } else if strings.HasPrefix(path, "/p/") { @@ -1528,7 +1537,7 @@ func (s *Server) makeStat(path string) plan9.Dir { // that check stat before reading (cat, 9pfuse, etc.) see non-zero size. if dir.Length == 0 && !isDir { switch { - case path == "/s/new" || path == "/s/idx" || path == "/s/ls" || path == "/s/kill": + case isSessionStoreFile(path): if content, err := s.sessionStore.Get(base); err == nil { dir.Length = uint64(len(content)) } diff --git a/internal/p9/sessionstore.go b/internal/p9/sessionstore.go index 1ec9d15..e8e1960 100644 --- a/internal/p9/sessionstore.go +++ b/internal/p9/sessionstore.go @@ -4,8 +4,27 @@ import ( "fmt" "os" "strings" + + "ollie/pkg/paths" ) +// sessionStoreFiles maps fixed file entries in s/ to their permissions. +// Add an entry here to expose a new file; the server uses this map for +// routing, mode, and stat length. +var sessionStoreFiles = map[string]os.FileMode{ + "new": 0666, + "idx": 0444, + "ls": 0555, + "kill": 0555, + "sh": 0555, + "job": 0555, + "q": 0555, + "sched": 0555, +} + +// sessionStoreOrder defines the listing order for fixed s/ entries. +var sessionStoreOrder = []string{"new", "idx", "ls", "kill", "sh", "job", "q", "sched"} + // SessionStore implements Store for the /s/ directory. // Entries are session IDs (directories) plus the synthetic files "new" and "idx". // Put("new", data) creates a session; Delete(id) kills one; Rename renames one. @@ -14,11 +33,9 @@ type SessionStore struct { } func (s *SessionStore) List() ([]os.DirEntry, error) { - entries := []os.DirEntry{ - syntheticEntry("new", 0666), - syntheticEntry("idx", 0444), - syntheticEntry("ls", 0555), - syntheticEntry("kill", 0555), + entries := make([]os.DirEntry, 0, len(sessionStoreOrder)) + for _, name := range sessionStoreOrder { + entries = append(entries, syntheticEntry(name, sessionStoreFiles[name])) } s.srv.mu.RLock() for id := range s.srv.sessions { @@ -29,15 +46,8 @@ func (s *SessionStore) List() ([]os.DirEntry, error) { } func (s *SessionStore) Stat(name string) (os.FileInfo, error) { - switch name { - case "new": - return &syntheticFileInfo{name: "new", mode: 0666}, nil - case "idx": - return &syntheticFileInfo{name: "idx", mode: 0444}, nil - case "ls": - return &syntheticFileInfo{name: "ls", mode: 0555}, nil - case "kill": - return &syntheticFileInfo{name: "kill", mode: 0555}, nil + if mode, ok := sessionStoreFiles[name]; ok { + return &syntheticFileInfo{name: name, mode: mode}, nil } s.srv.mu.RLock() _, ok := s.srv.sessions[name] @@ -54,10 +64,8 @@ func (s *SessionStore) Get(name string) ([]byte, error) { return []byte("name=\ncwd=\nbackend=\nmodel=\nagent=\n"), nil case "idx": return s.index(), nil - case "ls": - return []byte("#!/usr/bin/env bash\ncat ${OLLIE}/s/idx\n"), nil - case "kill": - return []byte("#!/usr/bin/env bash\nrm -r ${OLLIE}/s/$1\n"), nil + case "ls", "kill", "sh", "job", "q", "sched": + return os.ReadFile(paths.CfgDir() + "/scripts/" + name) } return nil, fmt.Errorf("%s: not a readable file", name) }