1 // Simple RPC-like protocol: implementation of connection and basic actions
3 // Copyright (C) 2021-2023 Simon Ruderich
5 // This program is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program. If not, see <http://www.gnu.org/licenses/>.
28 "ruderich.org/simon/safcm"
32 Events <-chan ConnEvent
33 events chan<- ConnEvent // same as Events, to publish events
34 eventsWg sync.WaitGroup
46 type ConnEventType int
49 _ ConnEventType = iota
50 ConnEventStderr // stderr from spawned process
51 ConnEventDebug // debug message
52 ConnEventUpload // remote helper upload in progress
55 type ConnEvent struct {
60 // NewConn creates a new connection. Events in the returned struct must be
61 // regularly read or the connection will hang. This must be done before
62 // DialSSH is called to open a connection.
63 func NewConn(debug bool) *Conn {
64 ch := make(chan ConnEvent)
72 func (c *Conn) debugf(format string, a ...interface{}) {
76 c.events <- ConnEvent{
78 Data: fmt.Sprintf(format, a...),
82 // Wrap safcm.GobConn's Send() and Recv() to provide debug output.
84 // Send sends a single message to the remote.
85 func (c *Conn) Send(m safcm.Msg) error {
86 // No checks for invalid Conn, a stacktrace is more helpful
88 c.debugf("Send: sending %#v", m)
92 // Recv waits for a single message from the remote.
93 func (c *Conn) Recv() (safcm.Msg, error) {
94 // No checks for invalid Conn, a stacktrace is more helpful
96 c.debugf("Recv: waiting for message")
97 m, err := c.conn.Recv()
98 c.debugf("Recv: received msg=%#v err=%#v", m, err)
102 // Wait waits for the connection to terminate. It's safe to call Wait (and
103 // Kill) multiple times.
104 func (c *Conn) Wait() error {
105 // But check here because Wait() can be called multiple times
107 return fmt.Errorf("Dial*() not called or already terminated")
110 c.debugf("Wait: waiting for connection to terminate")
113 func (c *Conn) wait() error {
117 // Wait until we've received all events from the program's stderr.
119 // Notify consumers that no more events will occur.
121 // We cannot reuse this channel.
123 // Don't set c.Events to nil because this creates a data race when
124 // another thread is still waiting.
129 // Kill forcefully terminates the connection. It's safe to call Kill (and
130 // Wait) multiple times. Calling it before Dial*() was called will only close
131 // the Events channel.
132 func (c *Conn) Kill() error {
138 return fmt.Errorf("Dial*() not called or already terminated")
141 c.debugf("Kill: killing connection")
143 c.cmd.Process.Kill() //nolint:errcheck
147 func (c *Conn) handleStderrAsEvents(cmd *exec.Cmd) error {
148 // cmd may differ from c.cmd here!
149 stderr, err := cmd.StderrPipe()
156 r := bufio.NewReader(stderr)
158 x, err := r.ReadString('\n')
162 x = strings.TrimRight(x, "\n")
164 c.events <- ConnEvent{
165 Type: ConnEventStderr,