Skip to content

Commit 70bc6da

Browse files
authored
fix(systemctl): harden unit output parsing (#15)
1 parent 1b41926 commit 70bc6da

2 files changed

Lines changed: 85 additions & 10 deletions

File tree

‎helpers.go‎

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ func GetSocketsForServiceUnit(ctx context.Context, unit string, opts Options) ([
7979
if err != nil {
8080
return []string{}, err
8181
}
82+
serviceUnit := serviceUnitName(unit)
8283
lines := strings.Split(stdout, "\n")
8384
sockets := []string{}
8485
for _, line := range lines {
@@ -87,8 +88,8 @@ func GetSocketsForServiceUnit(ctx context.Context, unit string, opts Options) ([
8788
continue
8889
}
8990
socketUnit := fields[1]
90-
serviceUnit := fields[2]
91-
if serviceUnit == unit+".service" {
91+
socketServiceUnit := fields[2]
92+
if socketServiceUnit == serviceUnit {
9293
sockets = append(sockets, socketUnit)
9394
}
9495
}
@@ -128,24 +129,37 @@ func GetMaskedUnits(ctx context.Context, opts Options) ([]string, error) {
128129
if err != nil {
129130
return []string{}, errors.Join(err, filterErr(stderr))
130131
}
132+
return parseMaskedUnits(stdout), nil
133+
}
134+
135+
func parseMaskedUnits(stdout string) []string {
131136
lines := strings.Split(stdout, "\n")
132137
units := []string{}
133138
for _, line := range lines {
134-
if !strings.Contains(line, "masked") {
135-
continue
136-
}
137-
entry := strings.Split(line, " ")
139+
entry := strings.Fields(line)
138140
if len(entry) < 3 {
139141
continue
140142
}
141143
if entry[1] == "masked" {
142144
unit := entry[0]
143-
uName := strings.Split(unit, ".")
144-
unit = uName[0]
145-
units = append(units, unit)
145+
units = append(units, unitNameWithoutSuffix(unit))
146146
}
147147
}
148-
return units, nil
148+
return units
149+
}
150+
151+
func serviceUnitName(unit string) string {
152+
if HasValidUnitSuffix(unit) {
153+
return unit
154+
}
155+
return unit + ".service"
156+
}
157+
158+
func unitNameWithoutSuffix(unit string) string {
159+
for _, unitType := range UnitTypes {
160+
unit = strings.TrimSuffix(unit, "."+unitType)
161+
}
162+
return unit
149163
}
150164

151165
// IsSystemd checks if systemd is the current init system by reading /proc/1/comm.
@@ -163,6 +177,7 @@ func IsMasked(ctx context.Context, unit string, opts Options) (bool, error) {
163177
if err != nil {
164178
return false, err
165179
}
180+
unit = unitNameWithoutSuffix(unit)
166181
for _, u := range units {
167182
if u == unit {
168183
return true, nil

‎util_test.go‎

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,63 @@ func TestPrepareArgs(t *testing.T) {
6060
})
6161
}
6262
}
63+
64+
func TestParseMaskedUnits(t *testing.T) {
65+
stdout := `UNIT FILE STATE PRESET
66+
foo.service masked enabled
67+
bar-baz.timer disabled enabled
68+
foo.bar.service masked enabled
69+
quux@one.service masked -
70+
tabbed.service masked enabled
71+
72+
4 unit files listed.`
73+
74+
got := parseMaskedUnits(stdout)
75+
expected := []string{"foo", "foo.bar", "quux@one", "tabbed"}
76+
if !reflect.DeepEqual(got, expected) {
77+
t.Fatalf("parseMaskedUnits() = %v, want %v", got, expected)
78+
}
79+
}
80+
81+
func TestServiceUnitName(t *testing.T) {
82+
tests := []struct {
83+
name string
84+
unit string
85+
expected string
86+
}{
87+
{name: "bare service name", unit: "nginx", expected: "nginx.service"},
88+
{name: "service suffix", unit: "nginx.service", expected: "nginx.service"},
89+
{name: "timer suffix", unit: "backup.timer", expected: "backup.timer"},
90+
}
91+
92+
for _, tt := range tests {
93+
t.Run(tt.name, func(t *testing.T) {
94+
got := serviceUnitName(tt.unit)
95+
if got != tt.expected {
96+
t.Fatalf("serviceUnitName(%q) = %q, want %q", tt.unit, got, tt.expected)
97+
}
98+
})
99+
}
100+
}
101+
102+
func TestUnitNameWithoutSuffix(t *testing.T) {
103+
tests := []struct {
104+
name string
105+
unit string
106+
expected string
107+
}{
108+
{name: "bare name", unit: "nginx", expected: "nginx"},
109+
{name: "service suffix", unit: "nginx.service", expected: "nginx"},
110+
{name: "timer suffix", unit: "backup.timer", expected: "backup"},
111+
{name: "preserves dotted prefix", unit: "foo.bar.service", expected: "foo.bar"},
112+
}
113+
114+
for _, tt := range tests {
115+
t.Run(tt.name, func(t *testing.T) {
116+
got := unitNameWithoutSuffix(tt.unit)
117+
if got != tt.expected {
118+
t.Fatalf("unitNameWithoutSuffix(%q) = %q, want %q", tt.unit, got, tt.expected)
119+
}
120+
})
121+
}
122+
}

0 commit comments

Comments
 (0)