Skip to content

Commit fc915c0

Browse files
authored
⭐ Add Solaris support to the services resource (#6482)
* Add Solaris support to the services resource Grab service information on Solaris. Solaris has a lot of states so this does require mapping their states into our traditional states. I'm open to different ways of doing this that expose more data to users. Let me know how folks feel. Signed-off-by: Tim Smith <tsmith84@gmail.com> * Fix linting issue Signed-off-by: Tim Smith <tsmith84@gmail.com> --------- Signed-off-by: Tim Smith <tsmith84@gmail.com>
1 parent 9bf25db commit fc915c0

File tree

3 files changed

+406
-0
lines changed

3 files changed

+406
-0
lines changed

providers/os/resources/services/manager.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,8 @@ func ResolveManager(conn shared.Connection) (OSServiceManager, error) {
159159
osm = &OpenrcServiceManager{conn: conn}
160160
case asset.Platform.Name == "aix":
161161
osm = &AixServiceManager{conn: conn}
162+
case asset.Platform.Name == "solaris":
163+
osm = &SolarisSmfServiceManager{conn: conn}
162164
case asset.Platform.IsFamily("linux"): // fallback for other linux distros which we assume are systemd
163165
osm = ResolveSystemdServiceManager(conn)
164166
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
// Copyright (c) Mondoo, Inc.
2+
// SPDX-License-Identifier: BUSL-1.1
3+
4+
package services
5+
6+
import (
7+
"bufio"
8+
"io"
9+
"strings"
10+
11+
"go.mondoo.com/cnquery/v12/providers/os/connection/shared"
12+
)
13+
14+
// SolarisSmfServiceManager handles Solaris Service Management Facility (SMF)
15+
// https://docs.oracle.com/cd/E23824_01/html/821-1451/hbrunlevels-25516.html
16+
type SolarisSmfServiceManager struct {
17+
conn shared.Connection
18+
}
19+
20+
func (s *SolarisSmfServiceManager) Name() string {
21+
return "Solaris Service Management Facility"
22+
}
23+
24+
func (s *SolarisSmfServiceManager) List() ([]*Service, error) {
25+
cmd, err := s.conn.RunCommand("svcs -a")
26+
if err != nil {
27+
return nil, err
28+
}
29+
30+
return ParseSolarisSmfServices(cmd.Stdout), nil
31+
}
32+
33+
// smfService represents a parsed SMF service entry
34+
type smfService struct {
35+
State string
36+
FMRI string
37+
}
38+
39+
// ParseSolarisSmfServices parses the output of `svcs -a`
40+
// Example output:
41+
//
42+
// STATE STIME FMRI
43+
// online 22:01:55 svc:/network/ssh:default
44+
// disabled 22:01:40 svc:/network/dns/client:default
45+
// legacy_run 22:02:03 lrc:/etc/rc2_d/S89PRESERVE
46+
func ParseSolarisSmfServices(r io.Reader) []*Service {
47+
var services []*Service
48+
scanner := bufio.NewScanner(r)
49+
50+
// Skip header line: "STATE STIME FMRI"
51+
_ = scanner.Scan()
52+
53+
for scanner.Scan() {
54+
line := scanner.Text()
55+
entry := parseSmfLine(line)
56+
if entry == nil {
57+
continue
58+
}
59+
60+
running, enabled := smfStateToRunningEnabled(entry.State)
61+
services = append(services, &Service{
62+
Name: entry.FMRI,
63+
Installed: true,
64+
Running: running,
65+
Enabled: enabled,
66+
State: smfStateToServiceState(entry.State),
67+
Type: "smf",
68+
})
69+
}
70+
71+
return services
72+
}
73+
74+
// parseSmfLine parses a single line from svcs -a output
75+
// Format: STATE STIME FMRI
76+
// Fields are whitespace-separated, with STATE and STIME being fixed-width
77+
func parseSmfLine(line string) *smfService {
78+
fields := strings.Fields(line)
79+
if len(fields) < 3 {
80+
return nil
81+
}
82+
83+
return &smfService{
84+
State: fields[0],
85+
FMRI: fields[2],
86+
}
87+
}
88+
89+
// normalizeSmfState strips the asterisk suffix from transitioning states
90+
// (e.g., "online*" -> "online" when a service is starting/stopping)
91+
func normalizeSmfState(state string) string {
92+
return strings.TrimSuffix(state, "*")
93+
}
94+
95+
// smfStateToRunningEnabled maps SMF states to running and enabled booleans
96+
// SMF states: online, offline, disabled, maintenance, degraded, legacy_run, incomplete, uninitialized
97+
func smfStateToRunningEnabled(state string) (running, enabled bool) {
98+
switch normalizeSmfState(state) {
99+
case "online":
100+
return true, true
101+
case "degraded":
102+
// Running but in a degraded state
103+
return true, true
104+
case "legacy_run":
105+
// Legacy init.d script is running
106+
return true, true
107+
case "offline":
108+
// Enabled but not running (waiting for dependencies)
109+
return false, true
110+
case "maintenance":
111+
// Enabled but in error state
112+
return false, true
113+
case "incomplete":
114+
// Service is in progress or waiting
115+
return false, true
116+
case "disabled", "uninitialized":
117+
return false, false
118+
default:
119+
// Unknown state, assume not running
120+
return false, false
121+
}
122+
}
123+
124+
// smfStateToServiceState maps SMF state to the generic ServiceState
125+
func smfStateToServiceState(state string) State {
126+
switch normalizeSmfState(state) {
127+
case "online", "degraded", "legacy_run":
128+
return ServiceRunning
129+
case "offline", "disabled", "maintenance", "incomplete", "uninitialized":
130+
return ServiceStopped
131+
default:
132+
return ServiceUnknown
133+
}
134+
}

0 commit comments

Comments
 (0)