1 // Simple RPC-like protocol: implementation of connection and basic actions
3 // SPDX-License-Identifier: GPL-3.0-or-later
4 // Copyright (C) 2021-2024 Simon Ruderich
16 "ruderich.org/simon/safcm"
20 Events <-chan ConnEvent
21 events chan<- ConnEvent // same as Events, to publish events
22 eventsWg sync.WaitGroup
34 type ConnEventType int
37 _ ConnEventType = iota
38 ConnEventStderr // stderr from spawned process
39 ConnEventDebug // debug message
40 ConnEventUpload // remote helper upload in progress
43 type ConnEvent struct {
48 // NewConn creates a new connection. Events in the returned struct must be
49 // regularly read or the connection will hang. This must be done before
50 // DialSSH is called to open a connection.
51 func NewConn(debug bool) *Conn {
52 ch := make(chan ConnEvent)
60 func (c *Conn) debugf(format string, a ...interface{}) {
64 c.events <- ConnEvent{
66 Data: fmt.Sprintf(format, a...),
70 // Wrap safcm.GobConn's Send() and Recv() to provide debug output.
72 // Send sends a single message to the remote.
73 func (c *Conn) Send(m safcm.Msg) error {
74 // No checks for invalid Conn, a stacktrace is more helpful
76 c.debugf("Send: sending %#v", m)
80 // Recv waits for a single message from the remote.
81 func (c *Conn) Recv() (safcm.Msg, error) {
82 // No checks for invalid Conn, a stacktrace is more helpful
84 c.debugf("Recv: waiting for message")
85 m, err := c.conn.Recv()
86 c.debugf("Recv: received msg=%#v err=%#v", m, err)
90 // Wait waits for the connection to terminate. It's safe to call Wait (and
91 // Kill) multiple times.
92 func (c *Conn) Wait() error {
93 // But check here because Wait() can be called multiple times
95 return fmt.Errorf("Dial*() not called or already terminated")
98 c.debugf("Wait: waiting for connection to terminate")
101 func (c *Conn) wait() error {
105 // Wait until we've received all events from the program's stderr.
107 // Notify consumers that no more events will occur.
109 // We cannot reuse this channel.
111 // Don't set c.Events to nil because this creates a data race when
112 // another thread is still waiting.
117 // Kill forcefully terminates the connection. It's safe to call Kill (and
118 // Wait) multiple times. Calling it before Dial*() was called will only close
119 // the Events channel.
120 func (c *Conn) Kill() error {
126 return fmt.Errorf("Dial*() not called or already terminated")
129 c.debugf("Kill: killing connection")
131 c.cmd.Process.Kill() //nolint:errcheck
135 func (c *Conn) handleStderrAsEvents(cmd *exec.Cmd) error {
136 // cmd may differ from c.cmd here!
137 stderr, err := cmd.StderrPipe()
144 r := bufio.NewReader(stderr)
146 x, err := r.ReadString('\n')
150 x = strings.TrimRight(x, "\n")
152 c.events <- ConnEvent{
153 Type: ConnEventStderr,