diff --git a/README.md b/README.md index 4500bb0..948407f 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ If your system isn't running (or targeting another system running) `systemctl`, ## Helper functionality - [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 restart count of a unit (`NRestarts`) as an int diff --git a/go.mod b/go.mod index 72c2245..6a43b6f 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module github.com/taigrr/systemctl -go 1.18 +go 1.26 diff --git a/helpers.go b/helpers.go index 5cf416f..ad62fcf 100644 --- a/helpers.go +++ b/helpers.go @@ -35,7 +35,7 @@ func GetNumRestarts(ctx context.Context, unit string, opts Options) (int, error) 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) { value, err := Show(ctx, unit, properties.MemoryCurrent, opts) if err != nil { @@ -56,6 +56,7 @@ func GetPID(ctx context.Context, unit string, opts Options) (int, error) { 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) { args := []string{"list-sockets", "--all", "--no-legend", "--no-pager"} if opts.UserMode { @@ -81,6 +82,7 @@ func GetSocketsForServiceUnit(ctx context.Context, unit string, opts Options) ([ return sockets, nil } +// GetUnits returns a list of all loaded units and their states. func GetUnits(ctx context.Context, opts Options) ([]Unit, error) { args := []string{"list-units", "--all", "--no-legend", "--full", "--no-pager"} if opts.UserMode { @@ -109,6 +111,7 @@ func GetUnits(ctx context.Context, opts Options) ([]Unit, error) { return units, nil } +// GetMaskedUnits returns a list of all masked unit names. func GetMaskedUnits(ctx context.Context, opts Options) ([]string, error) { args := []string{"list-unit-files", "--state=masked"} if opts.UserMode { @@ -138,7 +141,7 @@ func GetMaskedUnits(ctx context.Context, opts Options) ([]string, error) { 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) { b, err := os.ReadFile("/proc/1/comm") if err != nil { @@ -147,7 +150,7 @@ func IsSystemd() (bool, error) { 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) { units, err := GetMaskedUnits(ctx, opts) if err != nil { @@ -161,8 +164,8 @@ func IsMasked(ctx context.Context, unit string, opts Options) (bool, error) { return false, nil } -// check if a service is running -// https://unix.stackexchange.com/a/396633 +// IsRunning checks if a unit's sub-state is "running". +// See https://unix.stackexchange.com/a/396633 for details. func IsRunning(ctx context.Context, unit string, opts Options) (bool, error) { status, err := Show(ctx, unit, properties.SubState, opts) return status == "running", err diff --git a/structs.go b/structs.go index e758437..e0d4ca8 100644 --- a/structs.go +++ b/structs.go @@ -1,5 +1,7 @@ package systemctl +import "strings" + type Options struct { UserMode bool } @@ -12,7 +14,8 @@ type Unit struct { Description string } -var unitTypes = []string{ +// UnitTypes contains all valid systemd unit type suffixes. +var UnitTypes = []string{ "automount", "device", "mount", @@ -26,3 +29,14 @@ var unitTypes = []string{ "target", "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 +} diff --git a/systemctl.go b/systemctl.go index 8f36c65..1dc5f3d 100644 --- a/systemctl.go +++ b/systemctl.go @@ -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 // `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 func Status(ctx context.Context, unit string, opts Options) (string, error) { stat, err := status(ctx, unit, opts) diff --git a/systemctl_linux.go b/systemctl_linux.go index a38a73c..ad866c0 100644 --- a/systemctl_linux.go +++ b/systemctl_linux.go @@ -4,7 +4,6 @@ package systemctl import ( "context" - "regexp" "strings" "github.com/taigrr/systemctl/properties" @@ -148,14 +147,17 @@ func isFailed(ctx context.Context, unit string, opts Options) (bool, error) { args[1] = "--user" } stdout, _, _, err := execute(ctx, args) - if matched, _ := regexp.MatchString(`inactive`, stdout); matched { + stdout = strings.TrimSuffix(stdout, "\n") + switch stdout { + case "inactive": return false, nil - } else if matched, _ := regexp.MatchString(`active`, stdout); matched { + case "active": return false, nil - } else if matched, _ := regexp.MatchString(`failed`, stdout); matched { + case "failed": return true, nil + default: + return false, err } - return false, err } // 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 // `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 func status(ctx context.Context, unit string, opts Options) (string, error) { args := []string{"status", "--system", unit} diff --git a/util.go b/util.go index 2cc88cd..75f4d7a 100644 --- a/util.go +++ b/util.go @@ -11,6 +11,7 @@ import ( var systemctl string +// killed is the exit code returned when a process is terminated by SIGINT. const killed = 130 func init() { @@ -39,6 +40,10 @@ func execute(ctx context.Context, args []string) (string, string, int, error) { warnings = stderr.String() code = cmd.ProcessState.ExitCode() + if code == killed { + return output, warnings, code, ErrExecTimeout + } + customErr := filterErr(warnings) if customErr != nil { err = customErr