diff --git a/modules/clipmenu/cmd/clipmenu-tools/clipboard_test.go b/modules/clipmenu/cmd/clipmenu-tools/clipboard_test.go index 628a5527..2be67364 100644 --- a/modules/clipmenu/cmd/clipmenu-tools/clipboard_test.go +++ b/modules/clipmenu/cmd/clipmenu-tools/clipboard_test.go @@ -5,7 +5,6 @@ import ( "errors" "os" "os/exec" - "os/user" "path/filepath" "strconv" "strings" @@ -227,26 +226,21 @@ func TestWithoutAStoreTheChangesSayWhy(t *testing.T) { } } -// The tools can run as another account than the operator's; the daemon that counts is the operator's -// (issue 860). Here the operator is the test's own account and the tool runs as a different uid. -func TestCollectingIsTheOperatorsDaemonNotThatOfTheAccountTheToolRunsAs(t *testing.T) { +// clipmenud is a bash script: its process is named "bash" and only its command line names clipmenud +// (issue 860: collecting was false while the daemon ran). +func TestCollectingSeesClipmenudRunAsABashScript(t *testing.T) { store(t, map[string]string{"first": "first"}, "first") - fakeProcess(t, nobody, "clipmenud") // owned by the test's uid: the operator's - u, err := user.Current() - if err != nil { - t.Skip(err) + fakeProcess(t, nobody, "bash") + cmdline := filepath.Join(procRoot, strconv.Itoa(nobody), "cmdline") + if err := os.WriteFile(cmdline, []byte("bash\x00/usr/bin/clipmenud\x00"), 0o644); err != nil { + t.Fatal(err) } - t.Setenv("MESH_OPERATOR_ACCOUNT", u.Username) - old := toolUID - toolUID = func() int { return os.Getuid() + 1 } - t.Cleanup(func() { toolUID = old }) - h, err := History(10) - if err != nil || !h.Collecting { - t.Fatalf("the operator's clipmenud runs: %+v %v", h, err) + if h, err := History(10); err != nil || !h.Collecting { + t.Fatalf("clipmenud runs as a bash script: %+v %v", h, err) } - // without the operator named, the tool's own uid stands, and that account has no daemon - t.Setenv("MESH_OPERATOR_ACCOUNT", "") + // another bash script is not the daemon + os.WriteFile(cmdline, []byte("bash\x00/usr/bin/other\x00"), 0o644) if h, _ := History(10); h.Collecting { - t.Fatalf("another account's daemon counted: %+v", h) + t.Fatalf("a bash script that is not clipmenud counted: %+v", h) } } diff --git a/modules/clipmenu/cmd/clipmenu-tools/session.go b/modules/clipmenu/cmd/clipmenu-tools/session.go index 06f4fe24..55d66b2b 100644 --- a/modules/clipmenu/cmd/clipmenu-tools/session.go +++ b/modules/clipmenu/cmd/clipmenu-tools/session.go @@ -21,7 +21,6 @@ import ( "fmt" "os" "os/exec" - "os/user" "path/filepath" "sort" "strconv" @@ -387,31 +386,13 @@ func (c *capped) Write(p []byte) (int, error) { return c.Buffer.Write(p) } -// toolUID is the uid the tool runs as; a test replaces it to stand for a tool that runs as another -// account than the operator's. -var toolUID = os.Getuid - -// operatorUID is the uid of the operator's account, whose clipmenud and store these tools look at. -// The runtime serves the tools as an account that need not be the operator's and names the operator -// in MESH_OPERATOR_ACCOUNT; without it (or when it is unknown here) the tool's own uid stands. -func operatorUID() int { - if name := strings.TrimSpace(os.Getenv("MESH_OPERATOR_ACCOUNT")); name != "" { - if u, err := user.Lookup(name); err == nil { - if id, err := strconv.Atoi(u.Uid); err == nil { - return id - } - } - } - return toolUID() -} - -// processesOf are the pids of the operator account's processes whose command name is comm, oldest first. +// processesOf are the pids of this uid's processes whose command name is comm, oldest first. func processesOf(comm string) []int { entries, err := os.ReadDir(procRoot) if err != nil { return nil } - uid := operatorUID() + uid := os.Getuid() var pids []int for _, e := range entries { pid, err := strconv.Atoi(e.Name()) @@ -422,7 +403,7 @@ func processesOf(comm string) []int { if owner, ok := ownerOf(dir); !ok || owner != uid { continue } - if readTrimmed(filepath.Join(dir, "comm")) == comm { + if readTrimmed(filepath.Join(dir, "comm")) == comm || runsScript(dir, comm) { pids = append(pids, pid) } } @@ -430,7 +411,25 @@ func processesOf(comm string) []int { return pids } -// signalAll sends sig to every process of the operator account named comm, and answers the pids it reached. +// runsScript is whether the process is an interpreter running the script named name: clipmenud is a +// bash script, so its comm is "bash" and only its command line, "bash /usr/bin/clipmenud", names it. +func runsScript(procDir, name string) bool { + raw, err := os.ReadFile(filepath.Join(procDir, "cmdline")) + if err != nil { + return false + } + args := strings.Split(strings.TrimRight(string(raw), "\x00"), "\x00") + if len(args) < 2 { + return false + } + switch filepath.Base(args[0]) { + case "bash", "sh", "dash", "zsh", "env": + return filepath.Base(args[1]) == name + } + return false +} + +// signalAll sends sig to every process of this uid named comm, and answers the pids it reached. func signalAll(comm string, sig syscall.Signal) []int { var reached []int for _, pid := range processesOf(comm) {