diff options
author | Robin Jarry <robin@jarry.cc> | 2022-11-01 13:16:21 +0100 |
---|---|---|
committer | Robin Jarry <robin@jarry.cc> | 2022-11-02 13:18:19 +0100 |
commit | 7565a96525edb518129c7892843130bf0948eeb6 (patch) | |
tree | bdb967f0b4fae73856cf08a79b5ea93886b7d6e3 /completer | |
parent | ae99f4c5bb5af3b69410e2336c9f8204cda1d568 (diff) | |
download | aerc-7565a96525edb518129c7892843130bf0948eeb6.tar.gz |
address-book-cmd: ignore completion above 100 items
Avoid aerc from consuming all memory on the system if an address book
command returns 12 million addresses.
Read at most the first 100 lines and kill the command if it has not
finished. Display a warning in the logs for good measure.
The command is now assigned an different PGID (equal to its PID) to
allow killing it *and* all of its children. When the address book
command is a shell script that forks a process which never exits, it
will avoid killing the shell process and leaving its children without
parents.
Signed-off-by: Robin Jarry <robin@jarry.cc>
Tested-by: Bence Ferdinandy <bence@ferdinandy.com>
Diffstat (limited to 'completer')
-rw-r--r-- | completer/completer.go | 17 |
1 files changed, 17 insertions, 0 deletions
diff --git a/completer/completer.go b/completer/completer.go index bbeb5f1c..f3f7b9f7 100644 --- a/completer/completer.go +++ b/completer/completer.go @@ -10,7 +10,9 @@ import ( "os/exec" "regexp" "strings" + "syscall" + "git.sr.ht/~rjarry/aerc/logging" "github.com/google/shlex" ) @@ -71,6 +73,10 @@ func isAddressHeader(h string) bool { return false } +const maxCompletionLines = 100 + +var tooManyLines = fmt.Errorf("returned more than %d lines", maxCompletionLines) + // completeAddress uses the configured address book completion command to fetch // completions for the specified string, returning a slice of completions and // a prefix to be prepended to the selected completion, or an error. @@ -88,6 +94,8 @@ func (c *Completer) completeAddress(s string) ([]string, string, error) { if err != nil { return nil, "", fmt.Errorf("stderr: %w", err) } + // reset the process group id to allow killing all its children + cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} if err := cmd.Start(); err != nil { return nil, "", fmt.Errorf("cmd start: %w", err) } @@ -99,6 +107,12 @@ func (c *Completer) completeAddress(s string) ([]string, string, error) { completions, err := readCompletions(stdout) if err != nil { + // make sure to kill the process *and* all its children + //nolint:errcheck // who cares? + syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL) + logging.Warnf("command %s killed: %s", cmd, err) + } + if err != nil && !errors.Is(err, tooManyLines) { buf, _ := io.ReadAll(stderr) msg := strings.TrimSpace(string(buf)) if msg != "" { @@ -168,6 +182,9 @@ func readCompletions(r io.Reader) ([]string, error) { return nil, fmt.Errorf("could not decode MIME string: %w", err) } completions = append(completions, decoded) + if len(completions) >= maxCompletionLines { + return completions, tooManyLines + } } } |