The last setuid search's start and each library's own take ask are keys of their own, which no number of other acts moves (issue 774, ADR 0563 §2, §4; rev-l3 round 1)
This commit is contained in:
@@ -83,5 +83,14 @@ func TestALibrarySaysItsWaitWithoutWordsToParse(t *testing.T) {
|
||||
if len(asked) != 1 {
|
||||
t.Fatalf("the take was asked again after its ask ended: %d", len(asked))
|
||||
}
|
||||
// Kept in a key of its own (rev-l3 round 1): the acts gone from the record never make it ask again.
|
||||
keys, _ := state.Keys("act.")
|
||||
for _, k := range keys {
|
||||
_ = state.Delete(k)
|
||||
}
|
||||
askTakes(h, waiting, nil)
|
||||
if len(asked) != 1 {
|
||||
t.Fatalf("the take was asked again with the acts gone: %d", len(asked))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
+58
-10
@@ -329,17 +329,11 @@ func (h *HandActs) Search(caller, why string) (any, error) {
|
||||
if h.Searching != nil && h.Searching() {
|
||||
return nil, errors.New("a search for setuid programs runs on this machine now; no second is started while it does")
|
||||
}
|
||||
acts, err := h.read()
|
||||
if err != nil {
|
||||
// The last start is a key of its own, claimed by compare-and-set before the search starts: no number of other acts,
|
||||
// nor the record of acts, moves it (ADR 0563 §2).
|
||||
if err := h.claimSearch(now, caller); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := len(acts) - 1; i >= 0; i-- {
|
||||
a := acts[i]
|
||||
if a.Act == ActSetuidSearch && a.State == HandDone && now.Sub(a.At) < SearchEvery {
|
||||
return nil, fmt.Errorf("a search was started at %s by %s, and one is started in any hour: the next may start "+
|
||||
"at %s", a.At.Format(time.RFC3339), a.Caller, a.At.Add(SearchEvery).Format(time.RFC3339))
|
||||
}
|
||||
}
|
||||
said, err := h.SetuidSearch(caller)
|
||||
a := HandAct{ID: h.id(ActSetuidSearch), Act: ActSetuidSearch, Caller: caller, Why: why, At: now, State: HandDone, Said: said}
|
||||
if err != nil {
|
||||
@@ -354,6 +348,52 @@ func (h *HandActs) Search(caller, why string) (any, error) {
|
||||
return map[string]any{"node": h.Node, "said": said}, nil
|
||||
}
|
||||
|
||||
// searchKey keeps the last search's start on this node, and libKey each library's own take ask: keys of their own, which
|
||||
// no other act's key moves, kept for the bucket's age (ninety days, the node-engine's manifest).
|
||||
const searchKey = "search.last"
|
||||
|
||||
func libKey(access string) string {
|
||||
sum := sha256.Sum256([]byte(access))
|
||||
return "lib." + hex.EncodeToString(sum[:12]) + ".asked"
|
||||
}
|
||||
|
||||
// started is the last search's start as kept, and who asked it.
|
||||
type started struct {
|
||||
At time.Time `json:"at"`
|
||||
By string `json:"by"`
|
||||
}
|
||||
|
||||
// claimSearch refuses a search within SearchEvery of the last start, naming when the next may start, and otherwise
|
||||
// claims this one by compare-and-set: a second claim at the same moment loses.
|
||||
func (h *HandActs) claimSearch(now time.Time, caller string) error {
|
||||
e, err := h.State.Get(searchKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var rev uint64
|
||||
if e != nil {
|
||||
var last started
|
||||
if err := json.Unmarshal(e.Value, &last); err != nil {
|
||||
return fmt.Errorf("the last search's start cannot be read, so none is started: %w", err)
|
||||
}
|
||||
if now.Sub(last.At) < SearchEvery {
|
||||
return fmt.Errorf("a search was started at %s by %s, and one is started in any hour: the next may start "+
|
||||
"at %s", last.At.Format(time.RFC3339), last.By, last.At.Add(SearchEvery).Format(time.RFC3339))
|
||||
}
|
||||
rev = e.Revision
|
||||
}
|
||||
claim := started{At: now, By: caller}
|
||||
if rev == 0 {
|
||||
_, err = h.State.Create(searchKey, claim)
|
||||
} else {
|
||||
_, err = h.State.Update(searchKey, claim, rev)
|
||||
}
|
||||
if errors.Is(err, stdio.ErrChanged) {
|
||||
return errors.New("another search was started at this moment; no second is started")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// ---- the asks (§3, §4) --------------------------------------------------------------------------------------------
|
||||
|
||||
func (h *HandActs) id(act string) string {
|
||||
@@ -399,9 +439,13 @@ func (h *HandActs) AskAccess(caller, act, access, why string, auto bool) (any, e
|
||||
// AskedFor says whether an act on subject was ever asked by the engine itself: a library's take is asked by itself once
|
||||
// (ADR 0563 §4), and again only by hand.
|
||||
func (h *HandActs) AskedFor(act, subject string) bool {
|
||||
if act == ActAccess {
|
||||
e, err := h.State.Get(libKey(subject))
|
||||
return err != nil || e != nil // unread is never a reason to ask again
|
||||
}
|
||||
acts, err := h.read()
|
||||
if err != nil {
|
||||
return true // unread is never a reason to ask again
|
||||
return true
|
||||
}
|
||||
for _, a := range acts {
|
||||
if a.Act == act && a.Subject == subject && (a.Auto || a.State == HandAsked) {
|
||||
@@ -465,6 +509,10 @@ func (h *HandActs) ask(act, caller, why string, auto bool, j Judged) (any, error
|
||||
_, _, _ = h.end(a.Ask, func(x *HandAct) { x.State, x.Said = HandRefused, "the operator could not be asked: "+err.Error() })
|
||||
return nil, fmt.Errorf("the operator could not be asked, and nothing changed: %w", err)
|
||||
}
|
||||
// Once asked, a library's own take ask is kept's own take ask: once, in a key of its own (ADR 0563 §4).
|
||||
if auto && act == ActAccess {
|
||||
_, _ = h.State.Create(libKey(j.Subject), map[string]any{"ask": a.Ask, "at": now})
|
||||
}
|
||||
h.say(HandAsked, a)
|
||||
return map[string]any{"asked": a.Ask, "node": h.Node, "says": "the operator is asked on their channel; only " +
|
||||
"their yes changes anything. acts shows where it stands."}, nil
|
||||
|
||||
@@ -86,5 +86,24 @@ func TestASetuidSearchAskIsBelievedOnlySignedForThisNodeAndOnce(t *testing.T) {
|
||||
if got := acts.(map[string]any)["acts"].([]HandAct); len(got) != 1 || got[0].Caller != "ace.node-tools" || got[0].State != HandDone {
|
||||
t.Errorf("the last search is recorded as %+v", got)
|
||||
}
|
||||
// The bound is kept in a key of its own (rev-l3 round 1): two hundred judgements before a search, or the acts
|
||||
// gone from the record, never let a second search start within the hour.
|
||||
h.JudgeDirectories = func(string) (string, error) { return "judged", nil }
|
||||
for range 200 {
|
||||
if _, err := h.Call(EngineCall{Verb: ActJudgeDirectories, Caller: "ace.node-tools"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
at = now.Add(time.Hour + 2*time.Minute)
|
||||
if _, err := h.Call(EngineCall{Verb: ActSetuidSearch, Caller: "ace.node-tools"}); err == nil || !strings.Contains(err.Error(), "the next may start") {
|
||||
t.Errorf("a search after two hundred judgements: %v", err)
|
||||
}
|
||||
keys, _ := h.State.Keys("act.")
|
||||
for _, k := range keys {
|
||||
_ = h.State.Delete(k)
|
||||
}
|
||||
if _, err := h.Call(EngineCall{Verb: ActSetuidSearch, Caller: "ace.node-tools"}); err == nil || !strings.Contains(err.Error(), "the next may start") || started != 2 {
|
||||
t.Errorf("a search with the acts gone: %v, %d started", err, started)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user