9p: numeric uname principal, atomic UID counter

Session uname is now a numeric UID (>=10000) separate from the session
name. Rename no longer affects identity — the mount keeps working with
the same principal.

- SessionStore uses atomic.Uint32 for UID generation
- Session.Uname() returns the immutable principal
- SessionByUname() lookup for attach-time group assignment
- fileOwnerGroup uses principal, not session name
- WithMount() passed from createSession
This commit is contained in:
Levi Neely 2026-05-19 15:06:26 +02:00
parent a25dab6ee5
commit 4cf8a666e3
2 changed files with 36 additions and 9 deletions

View File

@ -279,6 +279,8 @@ func New(sink *olog.Sink, opts ...ServerOption) *Server {
return storeWrite(s.transcriptStore, name, data)
},
OnRename: func(oldID, newID string) {
s.RemoveGroup("agent", oldID)
s.AddGroup("agent", newID)
oldPrefix := "/s/" + oldID
newPrefix := "/s/" + newID
for _, c := range s.conns {
@ -326,15 +328,14 @@ func (s *Server) InGroup(group, user string) bool {
}
// fileOwnerGroup returns the uid and gid for a given path.
// Session files are owned by the session ID with group "agent".
// Session files are owned by the session's principal with group "agent".
// Everything else is owned by "ollie" with group "ollie".
func (s *Server) fileOwnerGroup(path string) (uid, gid string) {
if strings.HasPrefix(path, "/s/") {
parts := strings.SplitN(strings.TrimPrefix(path, "/s/"), "/", 2)
if len(parts) >= 1 && parts[0] != "new" {
// Check if parts[0] is a session (not a script like sh, bfg, etc.)
if sess := s.sessionStore.Session(parts[0]); sess != nil {
return parts[0], "agent"
return sess.Uname(), "agent"
}
}
}
@ -771,9 +772,9 @@ func (s *Server) attach(cs *connState, fc *plan9.Fcall) *plan9.Fcall {
qid := plan9.Qid{Type: QTDir, Path: 0}
cs.fids[fc.Fid] = &fid{path: "/", qid: qid}
s.log.Debug("Tattach uname=%q", fc.Uname)
// Assign group membership based on whether uname is a session ID.
// Assign group membership based on whether uname is a session principal.
if fc.Uname != "" {
if sess := s.sessionStore.Session(fc.Uname); sess != nil {
if s.sessionStore.SessionByUname(fc.Uname) != nil {
s.AddGroup("agent", fc.Uname)
} else {
s.AddGroup("user", fc.Uname)

View File

@ -7,6 +7,7 @@ import (
"sort"
"strings"
"sync"
"sync/atomic"
"ollie/pkg/agent"
"ollie/pkg/backend"
@ -21,6 +22,7 @@ import (
type Session struct {
mu sync.RWMutex
id string
uname string // immutable user principal (numeric UID), set at creation
Core agent.Core
Ctx context.Context
cancel context.CancelFunc
@ -37,6 +39,7 @@ func NewSession(id string, core agent.Core, ctx context.Context, cancel context.
}
func (sess *Session) RunnableID() string { return sess.id }
func (sess *Session) Uname() string { return sess.uname }
func (sess *Session) Cancel() {
sess.cancel()
@ -120,9 +123,15 @@ type SessionStoreConfig struct {
// SessionStore implements Store for session management.
type SessionStore struct {
*storeConfig
cfg SessionStoreConfig
mu sync.RWMutex
sessions map[string]*Session
cfg SessionStoreConfig
mu sync.RWMutex
sessions map[string]*Session
nextUID atomic.Uint32 // incrementing principal counter
}
// nextUname generates the next uname atomically.
func (s *SessionStore) nextUname() string {
return fmt.Sprintf("%d", s.nextUID.Add(1))
}
func NewSessionStore(cfg SessionStoreConfig) *SessionStore {
@ -136,6 +145,7 @@ func NewSessionStore(cfg SessionStoreConfig) *SessionStore {
cfg: cfg,
sessions: make(map[string]*Session),
}
ss.nextUID.Store(9999)
ss.storeConfig = &storeConfig{
StatFn: ss.stat,
ListFn: ss.list,
@ -236,6 +246,18 @@ func (s *SessionStore) Session(id string) *Session {
return s.sessions[id]
}
// SessionByUname returns the session with the given uname (principal), or nil.
func (s *SessionStore) SessionByUname(uname string) *Session {
s.mu.RLock()
defer s.mu.RUnlock()
for _, sess := range s.sessions {
if sess.uname == uname {
return sess
}
}
return nil
}
// OpenStore returns a RunnableStore for the given session ID.
func (s *SessionStore) OpenStore(id string) (RunnableStore, error) {
if sess := s.Session(id); sess != nil {
@ -358,6 +380,7 @@ func (s *SessionStore) createSession(args []string) error {
var core agent.Core
var allowTools []string
uname := s.nextUname()
if s.cfg.NewCore != nil {
var err error
core, err = s.cfg.NewCore(sessID, agentName, cwd)
@ -393,6 +416,7 @@ func (s *SessionStore) createSession(args []string) error {
}
var execOpts []execute.Option
execOpts = append(execOpts, execute.WithMount())
if s.cfg.Strict {
execOpts = append(execOpts, execute.WithStrict())
}
@ -409,7 +433,7 @@ func (s *SessionStore) createSession(args []string) error {
"execute": execute.Decl(cwd, execOpts...),
})
env := agent.BuildAgentEnv(cfg, newDisp(), cwd, []string{"OLLIE_SESSION_ID=" + sessID})
env := agent.BuildAgentEnv(cfg, newDisp(), cwd, []string{"OLLIE_SESSION_ID=" + sessID, "OLLIE_UNAME=" + uname})
core = agent.NewAgentCore(agent.AgentCoreConfig{
Backend: be,
@ -417,6 +441,7 @@ func (s *SessionStore) createSession(args []string) error {
AgentsDir: s.cfg.AgentsDir,
SessionsDir: s.cfg.SessionsDir,
SessionID: sessID,
Uname: uname,
CWD: cwd,
Env: env,
NewDispatcher: newDisp,
@ -429,6 +454,7 @@ func (s *SessionStore) createSession(args []string) error {
sess.AllowTools = allowTools
s.mu.Lock()
sess.uname = uname
s.sessions[sessID] = sess
s.mu.Unlock()