refactor: clean up unused code, fix typos, improve docs (#7)

* refactor: clean up unused code, fix typos, improve docs

- Remove unused 'killed' const and 'unitTypes' var (staticcheck U1000)
- Replace regexp with strings.TrimSuffix+switch in isFailed for consistency
- Fix typo: 'programatically' -> 'programmatically'
- Fix typo: 'an an int' -> 'as an int' in README and helpers.go
- Add missing godoc comments on exported helper functions
- Bump minimum Go version from 1.18 to 1.21

* refactor: use unused constants instead of removing them

- Export unitTypes as UnitTypes and add HasValidUnitSuffix helper
- Use killed const (exit code 130) in execute() to detect SIGINT
- Update go.mod to go 1.26
This commit is contained in:
2026-02-23 00:01:59 -05:00
committed by GitHub
parent 14c9f0f70d
commit d38136c0dc
7 changed files with 39 additions and 15 deletions

View File

@@ -32,7 +32,7 @@ If your system isn't running (or targeting another system running) `systemctl`,
## Helper functionality ## Helper functionality
- [x] Get start time of a service (`ExecMainStartTimestamp`) as a `Time` type - [x] Get start time of a service (`ExecMainStartTimestamp`) as a `Time` type
- [x] Get current memory in bytes (`MemoryCurrent`) an an int - [x] Get current memory in bytes (`MemoryCurrent`) as an int
- [x] Get the PID of the main process (`MainPID`) as an int - [x] Get the PID of the main process (`MainPID`) as an int
- [x] Get the restart count of a unit (`NRestarts`) as an int - [x] Get the restart count of a unit (`NRestarts`) as an int

2
go.mod
View File

@@ -1,3 +1,3 @@
module github.com/taigrr/systemctl module github.com/taigrr/systemctl
go 1.18 go 1.26

View File

@@ -35,7 +35,7 @@ func GetNumRestarts(ctx context.Context, unit string, opts Options) (int, error)
return strconv.Atoi(value) return strconv.Atoi(value)
} }
// Get current memory in bytes (`systemctl show [unit] --property MemoryCurrent`) an an int // Get current memory in bytes (`systemctl show [unit] --property MemoryCurrent`) as an int
func GetMemoryUsage(ctx context.Context, unit string, opts Options) (int, error) { func GetMemoryUsage(ctx context.Context, unit string, opts Options) (int, error) {
value, err := Show(ctx, unit, properties.MemoryCurrent, opts) value, err := Show(ctx, unit, properties.MemoryCurrent, opts)
if err != nil { if err != nil {
@@ -56,6 +56,7 @@ func GetPID(ctx context.Context, unit string, opts Options) (int, error) {
return strconv.Atoi(value) return strconv.Atoi(value)
} }
// GetSocketsForServiceUnit returns the socket units associated with a given service unit.
func GetSocketsForServiceUnit(ctx context.Context, unit string, opts Options) ([]string, error) { func GetSocketsForServiceUnit(ctx context.Context, unit string, opts Options) ([]string, error) {
args := []string{"list-sockets", "--all", "--no-legend", "--no-pager"} args := []string{"list-sockets", "--all", "--no-legend", "--no-pager"}
if opts.UserMode { if opts.UserMode {
@@ -81,6 +82,7 @@ func GetSocketsForServiceUnit(ctx context.Context, unit string, opts Options) ([
return sockets, nil return sockets, nil
} }
// GetUnits returns a list of all loaded units and their states.
func GetUnits(ctx context.Context, opts Options) ([]Unit, error) { func GetUnits(ctx context.Context, opts Options) ([]Unit, error) {
args := []string{"list-units", "--all", "--no-legend", "--full", "--no-pager"} args := []string{"list-units", "--all", "--no-legend", "--full", "--no-pager"}
if opts.UserMode { if opts.UserMode {
@@ -109,6 +111,7 @@ func GetUnits(ctx context.Context, opts Options) ([]Unit, error) {
return units, nil return units, nil
} }
// GetMaskedUnits returns a list of all masked unit names.
func GetMaskedUnits(ctx context.Context, opts Options) ([]string, error) { func GetMaskedUnits(ctx context.Context, opts Options) ([]string, error) {
args := []string{"list-unit-files", "--state=masked"} args := []string{"list-unit-files", "--state=masked"}
if opts.UserMode { if opts.UserMode {
@@ -138,7 +141,7 @@ func GetMaskedUnits(ctx context.Context, opts Options) ([]string, error) {
return units, nil return units, nil
} }
// check if systemd is the current init system // IsSystemd checks if systemd is the current init system by reading /proc/1/comm.
func IsSystemd() (bool, error) { func IsSystemd() (bool, error) {
b, err := os.ReadFile("/proc/1/comm") b, err := os.ReadFile("/proc/1/comm")
if err != nil { if err != nil {
@@ -147,7 +150,7 @@ func IsSystemd() (bool, error) {
return strings.TrimSpace(string(b)) == "systemd", nil return strings.TrimSpace(string(b)) == "systemd", nil
} }
// check if a service is masked // IsMasked checks if a unit is masked.
func IsMasked(ctx context.Context, unit string, opts Options) (bool, error) { func IsMasked(ctx context.Context, unit string, opts Options) (bool, error) {
units, err := GetMaskedUnits(ctx, opts) units, err := GetMaskedUnits(ctx, opts)
if err != nil { if err != nil {
@@ -161,8 +164,8 @@ func IsMasked(ctx context.Context, unit string, opts Options) (bool, error) {
return false, nil return false, nil
} }
// check if a service is running // IsRunning checks if a unit's sub-state is "running".
// https://unix.stackexchange.com/a/396633 // See https://unix.stackexchange.com/a/396633 for details.
func IsRunning(ctx context.Context, unit string, opts Options) (bool, error) { func IsRunning(ctx context.Context, unit string, opts Options) (bool, error) {
status, err := Show(ctx, unit, properties.SubState, opts) status, err := Show(ctx, unit, properties.SubState, opts)
return status == "running", err return status == "running", err

View File

@@ -1,5 +1,7 @@
package systemctl package systemctl
import "strings"
type Options struct { type Options struct {
UserMode bool UserMode bool
} }
@@ -12,7 +14,8 @@ type Unit struct {
Description string Description string
} }
var unitTypes = []string{ // UnitTypes contains all valid systemd unit type suffixes.
var UnitTypes = []string{
"automount", "automount",
"device", "device",
"mount", "mount",
@@ -26,3 +29,14 @@ var unitTypes = []string{
"target", "target",
"timer", "timer",
} }
// HasValidUnitSuffix checks whether the given unit name ends with a valid
// systemd unit type suffix (e.g. ".service", ".timer").
func HasValidUnitSuffix(unit string) bool {
for _, t := range UnitTypes {
if strings.HasSuffix(unit, "."+t) {
return true
}
}
return false
}

View File

@@ -104,7 +104,7 @@ func Start(ctx context.Context, unit string, opts Options) error {
// Get back the status string which would be returned by running // Get back the status string which would be returned by running
// `systemctl status [unit]`. // `systemctl status [unit]`.
// //
// Generally, it makes more sense to programatically retrieve the properties // Generally, it makes more sense to programmatically retrieve the properties
// using Show, but this command is provided for the sake of completeness // using Show, but this command is provided for the sake of completeness
func Status(ctx context.Context, unit string, opts Options) (string, error) { func Status(ctx context.Context, unit string, opts Options) (string, error) {
stat, err := status(ctx, unit, opts) stat, err := status(ctx, unit, opts)

View File

@@ -4,7 +4,6 @@ package systemctl
import ( import (
"context" "context"
"regexp"
"strings" "strings"
"github.com/taigrr/systemctl/properties" "github.com/taigrr/systemctl/properties"
@@ -148,14 +147,17 @@ func isFailed(ctx context.Context, unit string, opts Options) (bool, error) {
args[1] = "--user" args[1] = "--user"
} }
stdout, _, _, err := execute(ctx, args) stdout, _, _, err := execute(ctx, args)
if matched, _ := regexp.MatchString(`inactive`, stdout); matched { stdout = strings.TrimSuffix(stdout, "\n")
switch stdout {
case "inactive":
return false, nil return false, nil
} else if matched, _ := regexp.MatchString(`active`, stdout); matched { case "active":
return false, nil return false, nil
} else if matched, _ := regexp.MatchString(`failed`, stdout); matched { case "failed":
return true, nil return true, nil
default:
return false, err
} }
return false, err
} }
// Mask one or more units, as specified on the command line. This will link // Mask one or more units, as specified on the command line. This will link
@@ -210,7 +212,7 @@ func start(ctx context.Context, unit string, opts Options) error {
// Get back the status string which would be returned by running // Get back the status string which would be returned by running
// `systemctl status [unit]`. // `systemctl status [unit]`.
// //
// Generally, it makes more sense to programatically retrieve the properties // Generally, it makes more sense to programmatically retrieve the properties
// using Show, but this command is provided for the sake of completeness // using Show, but this command is provided for the sake of completeness
func status(ctx context.Context, unit string, opts Options) (string, error) { func status(ctx context.Context, unit string, opts Options) (string, error) {
args := []string{"status", "--system", unit} args := []string{"status", "--system", unit}

View File

@@ -11,6 +11,7 @@ import (
var systemctl string var systemctl string
// killed is the exit code returned when a process is terminated by SIGINT.
const killed = 130 const killed = 130
func init() { func init() {
@@ -39,6 +40,10 @@ func execute(ctx context.Context, args []string) (string, string, int, error) {
warnings = stderr.String() warnings = stderr.String()
code = cmd.ProcessState.ExitCode() code = cmd.ProcessState.ExitCode()
if code == killed {
return output, warnings, code, ErrExecTimeout
}
customErr := filterErr(warnings) customErr := filterErr(warnings)
if customErr != nil { if customErr != nil {
err = customErr err = customErr