• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mendersoftware / mender-connect / 2112616778

02 Sep 2025 11:04AM UTC coverage: 69.56% (-0.1%) from 69.672%
2112616778

push

gitlab-ci

web-flow
Merge pull request #161 from mendersoftware/dependabot/go_modules/github.com/stretchr/testify-1.11.1

chore: bump github.com/stretchr/testify from 1.10.0 to 1.11.1

2484 of 3571 relevant lines covered (69.56%)

6.23 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

65.22
/app/daemon_shell.go
1
// Copyright 2022 Northern.tech AS
2
//
3
//        Licensed under the Apache License, Version 2.0 (the "License");
4
//        you may not use this file except in compliance with the License.
5
//        You may obtain a copy of the License at
6
//
7
//            http://www.apache.org/licenses/LICENSE-2.0
8
//
9
//        Unless required by applicable law or agreed to in writing, software
10
//        distributed under the License is distributed on an "AS IS" BASIS,
11
//        WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
//        See the License for the specific language governing permissions and
13
//        limitations under the License.
14
package app
15

16
import (
17
        "fmt"
18

19
        "github.com/mendersoftware/go-lib-micro/ws"
20
        wsshell "github.com/mendersoftware/go-lib-micro/ws/shell"
21
        "github.com/pkg/errors"
22
        log "github.com/sirupsen/logrus"
23

24
        "github.com/mendersoftware/mender-connect/config"
25
        "github.com/mendersoftware/mender-connect/procps"
26
        "github.com/mendersoftware/mender-connect/session"
27
        "github.com/mendersoftware/mender-connect/utils"
28
)
29

30
const (
31
        propertyTerminalHeight = "terminal_height"
32
        propertyTerminalWidth  = "terminal_width"
33
        propertyUserID         = "user_id"
34
)
35

36
func getUserIdFromMessage(message *ws.ProtoMsg) string {
7✔
37
        userID, _ := message.Header.Properties[propertyUserID].(string)
7✔
38
        return userID
7✔
39
}
7✔
40

41
func (d *MenderShellDaemon) routeMessageSpawnShell(message *ws.ProtoMsg) error {
8✔
42
        var err error
8✔
43
        response := &ws.ProtoMsg{
8✔
44
                Header: ws.ProtoHdr{
8✔
45
                        Proto:     message.Header.Proto,
8✔
46
                        MsgType:   message.Header.MsgType,
8✔
47
                        SessionID: message.Header.SessionID,
8✔
48
                        Properties: map[string]interface{}{
8✔
49
                                "status": wsshell.NormalMessage,
8✔
50
                        },
8✔
51
                },
8✔
52
                Body: []byte{},
8✔
53
        }
8✔
54
        if d.shellsSpawned >= config.MaxShellsSpawned {
9✔
55
                err = session.ErrSessionTooManyShellsAlreadyRunning
1✔
56
                d.routeMessageResponse(response, err)
1✔
57
                return err
1✔
58
        }
1✔
59
        s := session.MenderShellSessionGetById(message.Header.SessionID)
7✔
60
        if s == nil {
14✔
61
                userId := getUserIdFromMessage(message)
7✔
62
                if s, err = session.NewMenderShellSession(
7✔
63
                        message.Header.SessionID,
7✔
64
                        userId,
7✔
65
                        d.expireSessionsAfter,
7✔
66
                        d.expireSessionsAfterIdle,
7✔
67
                ); err != nil {
8✔
68
                        d.routeMessageResponse(response, err)
1✔
69
                        return err
1✔
70
                }
1✔
71
                log.Debugf("created a new session: %s", s.GetId())
6✔
72
        }
73

74
        response.Header.SessionID = s.GetId()
6✔
75

6✔
76
        terminalHeight := d.TerminalConfig.Height
6✔
77
        terminalWidth := d.TerminalConfig.Width
6✔
78

6✔
79
        requestedHeight, requestedWidth := mapPropertiesToTerminalHeightAndWidth(
6✔
80
                message.Header.Properties,
6✔
81
        )
6✔
82
        if requestedHeight > 0 && requestedWidth > 0 {
12✔
83
                terminalHeight = requestedHeight
6✔
84
                terminalWidth = requestedWidth
6✔
85
        }
6✔
86

87
        log.Debugf("starting shell session_id=%s", s.GetId())
6✔
88
        if err = s.StartShell(s.GetId(), session.MenderShellTerminalSettings{
6✔
89
                Uid:            uint32(d.uid),
6✔
90
                Gid:            uint32(d.gid),
6✔
91
                Gids:           d.gids,
6✔
92
                Shell:          d.shell,
6✔
93
                HomeDir:        d.homeDir,
6✔
94
                TerminalString: d.terminalString,
6✔
95
                Height:         terminalHeight,
6✔
96
                Width:          terminalWidth,
6✔
97
                ShellArguments: d.shellArguments,
6✔
98
        }); err != nil {
6✔
99
                err = errors.Wrap(err, "failed to start shell")
×
100
                d.routeMessageResponse(response, err)
×
101
                return err
×
102
        }
×
103

104
        log.Debug("Shell started")
6✔
105
        d.shellsSpawned++
6✔
106

6✔
107
        response.Body = []byte("Shell started")
6✔
108
        d.routeMessageResponse(response, err)
6✔
109
        return nil
6✔
110
}
111

112
func (d *MenderShellDaemon) routeMessageStopShell(message *ws.ProtoMsg) error {
4✔
113
        var err error
4✔
114
        response := &ws.ProtoMsg{
4✔
115
                Header: ws.ProtoHdr{
4✔
116
                        Proto:     message.Header.Proto,
4✔
117
                        MsgType:   message.Header.MsgType,
4✔
118
                        SessionID: message.Header.SessionID,
4✔
119
                        Properties: map[string]interface{}{
4✔
120
                                "status": wsshell.NormalMessage,
4✔
121
                        },
4✔
122
                },
4✔
123
                Body: []byte{},
4✔
124
        }
4✔
125

4✔
126
        if len(message.Header.SessionID) < 1 {
4✔
127
                userId := getUserIdFromMessage(message)
×
128
                if len(userId) < 1 {
×
129
                        err = errors.New("StopShellMessage: sessionId not given and userId empty")
×
130
                        d.routeMessageResponse(response, err)
×
131
                        return err
×
132
                }
×
133
                shellsStoppedCount, err := session.MenderShellStopByUserId(userId)
×
134
                if err == nil {
×
135
                        if shellsStoppedCount > d.shellsSpawned {
×
136
                                d.shellsSpawned = 0
×
137
                                err = errors.New(fmt.Sprintf("StopByUserId: the shells stopped count (%d) "+
×
138
                                        "greater than total shells spawned (%d). resetting shells "+
×
139
                                        "spawned to 0.", shellsStoppedCount, d.shellsSpawned))
×
140
                                d.routeMessageResponse(response, err)
×
141
                                return err
×
142
                        } else {
×
143
                                log.Debugf("StopByUserId: stopped %d shells.", shellsStoppedCount)
×
144
                                d.DecreaseSpawnedShellsCount(shellsStoppedCount)
×
145
                        }
×
146
                }
147
                d.routeMessageResponse(response, err)
×
148
                return err
×
149
        }
150

151
        s := session.MenderShellSessionGetById(message.Header.SessionID)
4✔
152
        if s == nil {
6✔
153
                err = errors.New(
2✔
154
                        fmt.Sprintf(
2✔
155
                                "routeMessage: StopShellMessage: session not found for id %s",
2✔
156
                                message.Header.SessionID,
2✔
157
                        ),
2✔
158
                )
2✔
159
                d.routeMessageResponse(response, err)
2✔
160
                return err
2✔
161
        }
2✔
162

163
        err = s.StopShell()
2✔
164
        if err != nil {
2✔
165
                if procps.ProcessExists(s.GetShellPid()) {
×
166
                        log.Errorf("could not terminate shell (pid %d) for session %s, user"+
×
167
                                "will not be able to start another one if the limit is reached.",
×
168
                                s.GetShellPid(),
×
169
                                s.GetId())
×
170
                        err = errors.New("could not terminate shell: " + err.Error() + ".")
×
171
                        d.routeMessageResponse(response, err)
×
172
                        return err
×
173
                } else {
×
174
                        log.Errorf("process error on exit: %s", err.Error())
×
175
                }
×
176
        }
177
        if d.shellsSpawned == 0 {
2✔
178
                log.Warn("can't decrement shellsSpawned count: it is 0.")
×
179
        } else {
2✔
180
                d.shellsSpawned--
2✔
181
        }
2✔
182
        err = session.MenderShellDeleteById(s.GetId())
2✔
183
        d.routeMessageResponse(response, err)
2✔
184
        return err
2✔
185
}
186

187
func (d *MenderShellDaemon) routeMessageShellCommand(message *ws.ProtoMsg) error {
3✔
188
        var err error
3✔
189
        response := &ws.ProtoMsg{
3✔
190
                Header: ws.ProtoHdr{
3✔
191
                        Proto:     message.Header.Proto,
3✔
192
                        MsgType:   message.Header.MsgType,
3✔
193
                        SessionID: message.Header.SessionID,
3✔
194
                        Properties: map[string]interface{}{
3✔
195
                                "status": wsshell.NormalMessage,
3✔
196
                        },
3✔
197
                },
3✔
198
                Body: []byte{},
3✔
199
        }
3✔
200

3✔
201
        s := session.MenderShellSessionGetById(message.Header.SessionID)
3✔
202
        if s == nil {
4✔
203
                err = session.ErrSessionNotFound
1✔
204
                d.routeMessageResponse(response, err)
1✔
205
                return err
1✔
206
        }
1✔
207
        err = s.ShellCommand(message)
2✔
208
        if err != nil {
2✔
209
                err = errors.Wrapf(
×
210
                        err,
×
211
                        "routeMessage: shell command execution error, session_id=%s",
×
212
                        message.Header.SessionID,
×
213
                )
×
214
                d.routeMessageResponse(response, err)
×
215
                return err
×
216
        }
×
217
        return nil
2✔
218
}
219

220
func mapPropertiesToTerminalHeightAndWidth(properties map[string]interface{}) (uint16, uint16) {
6✔
221
        var terminalHeight, terminalWidth uint16
6✔
222
        requestedHeight, requestedHeightOk := properties[propertyTerminalHeight]
6✔
223
        requestedWidth, requestedWidthOk := properties[propertyTerminalWidth]
6✔
224
        if requestedHeightOk && requestedWidthOk {
12✔
225
                if val, _ := utils.Num64(requestedHeight); val > 0 {
12✔
226
                        terminalHeight = uint16(val)
6✔
227
                }
6✔
228
                if val, _ := utils.Num64(requestedWidth); val > 0 {
12✔
229
                        terminalWidth = uint16(val)
6✔
230
                }
6✔
231
        }
232
        return terminalHeight, terminalWidth
6✔
233
}
234

235
func (d *MenderShellDaemon) routeMessageShellResize(message *ws.ProtoMsg) error {
×
236
        var err error
×
237

×
238
        s := session.MenderShellSessionGetById(message.Header.SessionID)
×
239
        if s == nil {
×
240
                err = session.ErrSessionNotFound
×
241
                log.Error(err.Error())
×
242
                return err
×
243
        }
×
244

245
        terminalHeight, terminalWidth := mapPropertiesToTerminalHeightAndWidth(
×
246
                message.Header.Properties,
×
247
        )
×
248
        if terminalHeight > 0 && terminalWidth > 0 {
×
249
                s.ResizeShell(terminalHeight, terminalWidth)
×
250
        }
×
251
        return nil
×
252
}
253

254
func (d *MenderShellDaemon) routeMessagePongShell(message *ws.ProtoMsg) error {
×
255
        var err error
×
256

×
257
        s := session.MenderShellSessionGetById(message.Header.SessionID)
×
258
        if s == nil {
×
259
                err = session.ErrSessionNotFound
×
260
                log.Error(err.Error())
×
261
                return err
×
262
        }
×
263

264
        s.HealthcheckPong()
×
265
        return nil
×
266
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc