8 Commits

Author SHA1 Message Date
8301b337cc 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
2026-02-23 04:58:07 +00:00
c967fcb09e 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
2026-02-18 08:23:50 +00:00
Dat Boi Diego
14c9f0f70d chore: add "support" for non-linux platforms (#6) 2025-09-20 19:34:22 -04:00
5f1537f8bc Merge pull request #4 from MatthiasKunnen/build-lines
Fix build lines conflict with go toolchain version
2025-05-23 14:48:39 -07:00
Matthias Kunnen
d38d347cc6 Fix build lines conflict with go toolchain version
21fce7918e adds build tags, however,
these specific tags are not supported in the go version set in go.mod (1.12).
See <https://go.dev/doc/go1.17#build-lines> and <https://go.dev/doc/go1.18#go-build-lines>.
2025-05-23 17:29:00 +02:00
14a2ca2acd add placehomer unexported unittypes for later 2025-02-18 00:34:16 -08:00
451a949ace add new properties, socket finder 2025-02-18 00:30:12 -08:00
21fce7918e add linux build tags to restrict compilation to linux targets 2025-02-13 16:49:49 -08:00
10 changed files with 814 additions and 387 deletions

View File

@@ -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

2
go.mod
View File

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

View File

@@ -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,33 @@ 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 {
args = append(args, "--user")
}
stdout, _, _, err := execute(ctx, args)
if err != nil {
return []string{}, err
}
lines := strings.Split(stdout, "\n")
sockets := []string{}
for _, line := range lines {
fields := strings.Fields(line)
if len(fields) < 3 {
continue
}
socketUnit := fields[1]
serviceUnit := fields[2]
if serviceUnit == unit+".service" {
sockets = append(sockets, socketUnit)
}
}
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 {
@@ -84,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 {
@@ -113,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 {
@@ -122,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 {
@@ -136,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

View File

@@ -3,6 +3,7 @@ package properties
type Property string
const (
Accept Property = "Accept"
ActiveEnterTimestamp Property = "ActiveEnterTimestamp"
ActiveEnterTimestampMonotonic Property = "ActiveEnterTimestampMonotonic"
ActiveExitTimestampMonotonic Property = "ActiveExitTimestampMonotonic"
@@ -12,9 +13,13 @@ const (
AssertResult Property = "AssertResult"
AssertTimestamp Property = "AssertTimestamp"
AssertTimestampMonotonic Property = "AssertTimestampMonotonic"
Backlog Property = "Backlog"
Before Property = "Before"
BindIPv6Only Property = "BindIPv6Only"
BindLogSockets Property = "BindLogSockets"
BlockIOAccounting Property = "BlockIOAccounting"
BlockIOWeight Property = "BlockIOWeight"
Broadcast Property = "Broadcast"
CPUAccounting Property = "CPUAccounting"
CPUAffinityFromNUMA Property = "CPUAffinityFromNUMA"
CPUQuotaPerSecUSec Property = "CPUQuotaPerSecUSec"
@@ -28,6 +33,7 @@ const (
CacheDirectoryMode Property = "CacheDirectoryMode"
CanFreeze Property = "CanFreeze"
CanIsolate Property = "CanIsolate"
CanLiveMount Property = "CanLiveMount"
CanReload Property = "CanReload"
CanStart Property = "CanStart"
CanStop Property = "CanStop"
@@ -40,17 +46,26 @@ const (
ConfigurationDirectoryMode Property = "ConfigurationDirectoryMode"
Conflicts Property = "Conflicts"
ControlGroup Property = "ControlGroup"
ControlGroupId Property = "ControlGroupId"
ControlPID Property = "ControlPID"
CoredumpFilter Property = "CoredumpFilter"
CoredumpReceive Property = "CoredumpReceive"
DebugInvocation Property = "DebugInvocation"
DefaultDependencies Property = "DefaultDependencies"
DefaultMemoryLow Property = "DefaultMemoryLow"
DefaultMemoryMin Property = "DefaultMemoryMin"
DefaultStartupMemoryLow Property = "DefaultStartupMemoryLow"
DeferAcceptUSec Property = "DeferAcceptUSec"
Delegate Property = "Delegate"
Description Property = "Description"
DevicePolicy Property = "DevicePolicy"
DirectoryMode Property = "DirectoryMode"
DynamicUser Property = "DynamicUser"
EffectiveCPUs Property = "EffectiveCPUs"
EffectiveMemoryHigh Property = "EffectiveMemoryHigh"
EffectiveMemoryMax Property = "EffectiveMemoryMax"
EffectiveMemoryNodes Property = "EffectiveMemoryNodes"
EffectiveTasksMax Property = "EffectiveTasksMax"
ExecMainCode Property = "ExecMainCode"
ExecMainExitTimestampMonotonic Property = "ExecMainExitTimestampMonotonic"
ExecMainPID Property = "ExecMainPID"
@@ -61,10 +76,14 @@ const (
ExecReloadEx Property = "ExecReloadEx"
ExecStart Property = "ExecStart"
ExecStartEx Property = "ExecStartEx"
ExtensionImagePolicy Property = "ExtensionImagePolicy"
FailureAction Property = "FailureAction"
FileDescriptorName Property = "FileDescriptorName"
FileDescriptorStoreMax Property = "FileDescriptorStoreMax"
FinalKillSignal Property = "FinalKillSignal"
FlushPending Property = "FlushPending"
FragmentPath Property = "FragmentPath"
FreeBind Property = "FreeBind"
FreezerState Property = "FreezerState"
GID Property = "GID"
GuessMainPID Property = "GuessMainPID"
@@ -81,6 +100,8 @@ const (
IPEgressPackets Property = "IPEgressPackets"
IPIngressBytes Property = "IPIngressBytes"
IPIngressPackets Property = "IPIngressPackets"
IPTOS Property = "IPTOS"
IPTTL Property = "IPTTL"
Id Property = "Id"
IgnoreOnIsolate Property = "IgnoreOnIsolate"
IgnoreSIGPIPE Property = "IgnoreSIGPIPE"
@@ -91,6 +112,10 @@ const (
JobRunningTimeoutUSec Property = "JobRunningTimeoutUSec"
JobTimeoutAction Property = "JobTimeoutAction"
JobTimeoutUSec Property = "JobTimeoutUSec"
KeepAlive Property = "KeepAlive"
KeepAliveIntervalUSec Property = "KeepAliveIntervalUSec"
KeepAliveProbes Property = "KeepAliveProbes"
KeepAliveTimeUSec Property = "KeepAliveTimeUSec"
KeyringMode Property = "KeyringMode"
KillMode Property = "KillMode"
KillSignal Property = "KillSignal"
@@ -126,6 +151,7 @@ const (
LimitSIGPENDINGSoft Property = "LimitSIGPENDINGSoft"
LimitSTACK Property = "LimitSTACK"
LimitSTACKSoft Property = "LimitSTACKSoft"
Listen Property = "Listen"
LoadState Property = "LoadState"
LockPersonality Property = "LockPersonality"
LogLevelMax Property = "LogLevelMax"
@@ -134,42 +160,76 @@ const (
LogsDirectoryMode Property = "LogsDirectoryMode"
MainPID Property = "MainPID"
ManagedOOMMemoryPressure Property = "ManagedOOMMemoryPressure"
ManagedOOMMemoryPressureDurationUSec Property = "ManagedOOMMemoryPressureDurationUSec"
ManagedOOMMemoryPressureLimit Property = "ManagedOOMMemoryPressureLimit"
ManagedOOMPreference Property = "ManagedOOMPreference"
ManagedOOMSwap Property = "ManagedOOMSwap"
Mark Property = "Mark"
MaxConnections Property = "MaxConnections"
MaxConnectionsPerSource Property = "MaxConnectionsPerSource"
MemoryAccounting Property = "MemoryAccounting"
MemoryAvailable Property = "MemoryAvailable"
MemoryCurrent Property = "MemoryCurrent"
MemoryDenyWriteExecute Property = "MemoryDenyWriteExecute"
MemoryHigh Property = "MemoryHigh"
MemoryKSM Property = "MemoryKSM"
MemoryLimit Property = "MemoryLimit"
MemoryLow Property = "MemoryLow"
MemoryMax Property = "MemoryMax"
MemoryMin Property = "MemoryMin"
MemoryPeak Property = "MemoryPeak"
MemoryPressureThresholdUSec Property = "MemoryPressureThresholdUSec"
MemoryPressureWatch Property = "MemoryPressureWatch"
MemorySwapCurrent Property = "MemorySwapCurrent"
MemorySwapMax Property = "MemorySwapMax"
MemorySwapPeak Property = "MemorySwapPeak"
MemoryZSwapCurrent Property = "MemoryZSwapCurrent"
MemoryZSwapMax Property = "MemoryZSwapMax"
MemoryZSwapWriteback Property = "MemoryZSwapWriteback"
MessageQueueMaxMessages Property = "MessageQueueMaxMessages"
MessageQueueMessageSize Property = "MessageQueueMessageSize"
MountAPIVFS Property = "MountAPIVFS"
MountImagePolicy Property = "MountImagePolicy"
NAccepted Property = "NAccepted"
NConnections Property = "NConnections"
NFileDescriptorStore Property = "NFileDescriptorStore"
NRefused Property = "NRefused"
NRestarts Property = "NRestarts"
NUMAPolicy Property = "NUMAPolicy"
Names Property = "Names"
NeedDaemonReload Property = "NeedDaemonReload"
Nice Property = "Nice"
NoDelay Property = "NoDelay"
NoNewPrivileges Property = "NoNewPrivileges"
NonBlocking Property = "NonBlocking"
NotifyAccess Property = "NotifyAccess"
OOMPolicy Property = "OOMPolicy"
OOMScoreAdjust Property = "OOMScoreAdjust"
OnFailureJobMode Property = "OnFailureJobMode"
OnSuccessJobMode Property = "OnSuccessJobMode"
PIDFile Property = "PIDFile"
PassCredentials Property = "PassCredentials"
PassFileDescriptorsToExec Property = "PassFileDescriptorsToExec"
PassPacketInfo Property = "PassPacketInfo"
PassSecurity Property = "PassSecurity"
Perpetual Property = "Perpetual"
PipeSize Property = "PipeSize"
PollLimitBurst Property = "PollLimitBurst"
PollLimitIntervalUSec Property = "PollLimitIntervalUSec"
Priority Property = "Priority"
PrivateDevices Property = "PrivateDevices"
PrivateIPC Property = "PrivateIPC"
PrivateMounts Property = "PrivateMounts"
PrivateNetwork Property = "PrivateNetwork"
PrivatePIDs Property = "PrivatePIDs"
PrivateTmp Property = "PrivateTmp"
PrivateTmpEx Property = "PrivateTmpEx"
PrivateUsers Property = "PrivateUsers"
PrivateUsersEx Property = "PrivateUsersEx"
ProcSubset Property = "ProcSubset"
ProtectClock Property = "ProtectClock"
ProtectControlGroups Property = "ProtectControlGroups"
ProtectControlGroupsEx Property = "ProtectControlGroupsEx"
ProtectHome Property = "ProtectHome"
ProtectHostname Property = "ProtectHostname"
ProtectKernelLogs Property = "ProtectKernelLogs"
@@ -177,12 +237,16 @@ const (
ProtectKernelTunables Property = "ProtectKernelTunables"
ProtectProc Property = "ProtectProc"
ProtectSystem Property = "ProtectSystem"
ReceiveBuffer Property = "ReceiveBuffer"
RefuseManualStart Property = "RefuseManualStart"
RefuseManualStop Property = "RefuseManualStop"
ReloadResult Property = "ReloadResult"
RemainAfterExit Property = "RemainAfterExit"
RemoveIPC Property = "RemoveIPC"
RemoveOnStop Property = "RemoveOnStop"
RequiredBy Property = "RequiredBy"
Requires Property = "Requires"
RequiresMountsFor Property = "RequiresMountsFor"
Restart Property = "Restart"
RestartKillSignal Property = "RestartKillSignal"
RestartUSec Property = "RestartUSec"
@@ -190,15 +254,22 @@ const (
RestrictRealtime Property = "RestrictRealtime"
RestrictSUIDSGID Property = "RestrictSUIDSGID"
Result Property = "Result"
ReusePort Property = "ReusePort"
RootDirectoryStartOnly Property = "RootDirectoryStartOnly"
RootEphemeral Property = "RootEphemeral"
RootImagePolicy Property = "RootImagePolicy"
RuntimeDirectoryMode Property = "RuntimeDirectoryMode"
RuntimeDirectoryPreserve Property = "RuntimeDirectoryPreserve"
RuntimeMaxUSec Property = "RuntimeMaxUSec"
SameProcessGroup Property = "SameProcessGroup"
SecureBits Property = "SecureBits"
SendBuffer Property = "SendBuffer"
SendSIGHUP Property = "SendSIGHUP"
SendSIGKILL Property = "SendSIGKILL"
SetLoginEnvironment Property = "SetLoginEnvironment"
Slice Property = "Slice"
SocketMode Property = "SocketMode"
SocketProtocol Property = "SocketProtocol"
StandardError Property = "StandardError"
StandardInput Property = "StandardInput"
StandardOutput Property = "StandardOutput"
@@ -209,6 +280,11 @@ const (
StartupCPUShares Property = "StartupCPUShares"
StartupCPUWeight Property = "StartupCPUWeight"
StartupIOWeight Property = "StartupIOWeight"
StartupMemoryHigh Property = "StartupMemoryHigh"
StartupMemoryLow Property = "StartupMemoryLow"
StartupMemoryMax Property = "StartupMemoryMax"
StartupMemorySwapMax Property = "StartupMemorySwapMax"
StartupMemoryZSwapMax Property = "StartupMemoryZSwapMax"
StateChangeTimestamp Property = "StateChangeTimestamp"
StateChangeTimestampMonotonic Property = "StateChangeTimestampMonotonic"
StateDirectoryMode Property = "StateDirectoryMode"
@@ -216,6 +292,7 @@ const (
StopWhenUnneeded Property = "StopWhenUnneeded"
SubState Property = "SubState"
SuccessAction Property = "SuccessAction"
SurviveFinalKillSignal Property = "SurviveFinalKillSignal"
SyslogFacility Property = "SyslogFacility"
SyslogLevel Property = "SyslogLevel"
SyslogLevelPrefix Property = "SyslogLevelPrefix"
@@ -233,8 +310,14 @@ const (
TimeoutStartUSec Property = "TimeoutStartUSec"
TimeoutStopFailureMode Property = "TimeoutStopFailureMode"
TimeoutStopUSec Property = "TimeoutStopUSec"
TimeoutUSec Property = "TimeoutUSec"
TimerSlackNSec Property = "TimerSlackNSec"
Timestamping Property = "Timestamping"
Transient Property = "Transient"
Transparent Property = "Transparent"
TriggerLimitBurst Property = "TriggerLimitBurst"
TriggerLimitIntervalUSec Property = "TriggerLimitIntervalUSec"
Triggers Property = "Triggers"
Type Property = "Type"
UID Property = "UID"
UMask Property = "UMask"
@@ -245,4 +328,5 @@ const (
WatchdogSignal Property = "WatchdogSignal"
WatchdogTimestampMonotonic Property = "WatchdogTimestampMonotonic"
WatchdogUSec Property = "WatchdogUSec"
Writable Property = "Writable"
)

View File

@@ -1,6 +1,7 @@
package properties
var Properties = []Property{
Accept,
ActiveEnterTimestamp,
ActiveEnterTimestampMonotonic,
ActiveExitTimestampMonotonic,
@@ -10,9 +11,13 @@ var Properties = []Property{
AssertResult,
AssertTimestamp,
AssertTimestampMonotonic,
Backlog,
Before,
BindIPv6Only,
BindLogSockets,
BlockIOAccounting,
BlockIOWeight,
Broadcast,
CPUAccounting,
CPUAffinityFromNUMA,
CPUQuotaPerSecUSec,
@@ -26,6 +31,7 @@ var Properties = []Property{
CacheDirectoryMode,
CanFreeze,
CanIsolate,
CanLiveMount,
CanReload,
CanStart,
CanStop,
@@ -38,17 +44,26 @@ var Properties = []Property{
ConfigurationDirectoryMode,
Conflicts,
ControlGroup,
ControlGroupId,
ControlPID,
CoredumpFilter,
CoredumpReceive,
DebugInvocation,
DefaultDependencies,
DefaultMemoryLow,
DefaultMemoryMin,
DefaultStartupMemoryLow,
DeferAcceptUSec,
Delegate,
Description,
DevicePolicy,
DirectoryMode,
DynamicUser,
EffectiveCPUs,
EffectiveMemoryHigh,
EffectiveMemoryMax,
EffectiveMemoryNodes,
EffectiveTasksMax,
ExecMainCode,
ExecMainExitTimestampMonotonic,
ExecMainPID,
@@ -59,10 +74,14 @@ var Properties = []Property{
ExecReloadEx,
ExecStart,
ExecStartEx,
ExtensionImagePolicy,
FailureAction,
FileDescriptorName,
FileDescriptorStoreMax,
FinalKillSignal,
FlushPending,
FragmentPath,
FreeBind,
FreezerState,
GID,
GuessMainPID,
@@ -79,6 +98,8 @@ var Properties = []Property{
IPEgressPackets,
IPIngressBytes,
IPIngressPackets,
IPTOS,
IPTTL,
Id,
IgnoreOnIsolate,
IgnoreSIGPIPE,
@@ -89,6 +110,10 @@ var Properties = []Property{
JobRunningTimeoutUSec,
JobTimeoutAction,
JobTimeoutUSec,
KeepAlive,
KeepAliveIntervalUSec,
KeepAliveProbes,
KeepAliveTimeUSec,
KeyringMode,
KillMode,
KillSignal,
@@ -124,6 +149,7 @@ var Properties = []Property{
LimitSIGPENDINGSoft,
LimitSTACK,
LimitSTACKSoft,
Listen,
LoadState,
LockPersonality,
LogLevelMax,
@@ -132,42 +158,76 @@ var Properties = []Property{
LogsDirectoryMode,
MainPID,
ManagedOOMMemoryPressure,
ManagedOOMMemoryPressureDurationUSec,
ManagedOOMMemoryPressureLimit,
ManagedOOMPreference,
ManagedOOMSwap,
Mark,
MaxConnections,
MaxConnectionsPerSource,
MemoryAccounting,
MemoryAvailable,
MemoryCurrent,
MemoryDenyWriteExecute,
MemoryHigh,
MemoryKSM,
MemoryLimit,
MemoryLow,
MemoryMax,
MemoryMin,
MemoryPeak,
MemoryPressureThresholdUSec,
MemoryPressureWatch,
MemorySwapCurrent,
MemorySwapMax,
MemorySwapPeak,
MemoryZSwapCurrent,
MemoryZSwapMax,
MemoryZSwapWriteback,
MessageQueueMaxMessages,
MessageQueueMessageSize,
MountAPIVFS,
MountImagePolicy,
NAccepted,
NConnections,
NFileDescriptorStore,
NRefused,
NRestarts,
NUMAPolicy,
Names,
NeedDaemonReload,
Nice,
NoDelay,
NoNewPrivileges,
NonBlocking,
NotifyAccess,
OOMPolicy,
OOMScoreAdjust,
OnFailureJobMode,
OnSuccessJobMode,
PIDFile,
PassCredentials,
PassFileDescriptorsToExec,
PassPacketInfo,
PassSecurity,
Perpetual,
PipeSize,
PollLimitBurst,
PollLimitIntervalUSec,
Priority,
PrivateDevices,
PrivateIPC,
PrivateMounts,
PrivateNetwork,
PrivatePIDs,
PrivateTmp,
PrivateTmpEx,
PrivateUsers,
PrivateUsersEx,
ProcSubset,
ProtectClock,
ProtectControlGroups,
ProtectControlGroupsEx,
ProtectHome,
ProtectHostname,
ProtectKernelLogs,
@@ -175,12 +235,16 @@ var Properties = []Property{
ProtectKernelTunables,
ProtectProc,
ProtectSystem,
ReceiveBuffer,
RefuseManualStart,
RefuseManualStop,
ReloadResult,
RemainAfterExit,
RemoveIPC,
RemoveOnStop,
RequiredBy,
Requires,
RequiresMountsFor,
Restart,
RestartKillSignal,
RestartUSec,
@@ -188,15 +252,22 @@ var Properties = []Property{
RestrictRealtime,
RestrictSUIDSGID,
Result,
ReusePort,
RootDirectoryStartOnly,
RootEphemeral,
RootImagePolicy,
RuntimeDirectoryMode,
RuntimeDirectoryPreserve,
RuntimeMaxUSec,
SameProcessGroup,
SecureBits,
SendBuffer,
SendSIGHUP,
SendSIGKILL,
SetLoginEnvironment,
Slice,
SocketMode,
SocketProtocol,
StandardError,
StandardInput,
StandardOutput,
@@ -207,6 +278,11 @@ var Properties = []Property{
StartupCPUShares,
StartupCPUWeight,
StartupIOWeight,
StartupMemoryHigh,
StartupMemoryLow,
StartupMemoryMax,
StartupMemorySwapMax,
StartupMemoryZSwapMax,
StateChangeTimestamp,
StateChangeTimestampMonotonic,
StateDirectoryMode,
@@ -214,6 +290,7 @@ var Properties = []Property{
StopWhenUnneeded,
SubState,
SuccessAction,
SurviveFinalKillSignal,
SyslogFacility,
SyslogLevel,
SyslogLevelPrefix,
@@ -231,8 +308,14 @@ var Properties = []Property{
TimeoutStartUSec,
TimeoutStopFailureMode,
TimeoutStopUSec,
TimeoutUSec,
TimerSlackNSec,
Timestamping,
Transient,
Transparent,
TriggerLimitBurst,
TriggerLimitIntervalUSec,
Triggers,
Type,
UID,
UMask,
@@ -243,4 +326,5 @@ var Properties = []Property{
WatchdogSignal,
WatchdogTimestampMonotonic,
WatchdogUSec,
Writable,
}

View File

@@ -1,5 +1,7 @@
package systemctl
import "strings"
type Options struct {
UserMode bool
}
@@ -11,3 +13,30 @@ type Unit struct {
Sub string
Description string
}
// UnitTypes contains all valid systemd unit type suffixes.
var UnitTypes = []string{
"automount",
"device",
"mount",
"path",
"scope",
"service",
"slice",
"snapshot",
"socket",
"swap",
"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
}

View File

@@ -2,8 +2,6 @@ package systemctl
import (
"context"
"regexp"
"strings"
"github.com/taigrr/systemctl/properties"
)
@@ -15,12 +13,7 @@ import (
// reloaded, all sockets systemd listens on behalf of user configuration will
// stay accessible.
func DaemonReload(ctx context.Context, opts Options) error {
args := []string{"daemon-reload", "--system"}
if opts.UserMode {
args[1] = "--user"
}
_, _, _, err := execute(ctx, args)
return err
return daemonReload(ctx, opts)
}
// Reenables one or more units.
@@ -29,12 +22,7 @@ func DaemonReload(ctx context.Context, opts Options) error {
// the unit configuration directory, then recreates the symlink to the unit again,
// atomically. Can be used to change the symlink target.
func Reenable(ctx context.Context, unit string, opts Options) error {
args := []string{"reenable", "--system", unit}
if opts.UserMode {
args[1] = "--user"
}
_, _, _, err := execute(ctx, args)
return err
return reenable(ctx, unit, opts)
}
// Disables one or more units.
@@ -43,12 +31,7 @@ func Reenable(ctx context.Context, unit string, opts Options) error {
// the unit configuration directory, and hence undoes any changes made by
// enable or link.
func Disable(ctx context.Context, unit string, opts Options) error {
args := []string{"disable", "--system", unit}
if opts.UserMode {
args[1] = "--user"
}
_, _, _, err := execute(ctx, args)
return err
return disable(ctx, unit, opts)
}
// Enable one or more units or unit instances.
@@ -58,12 +41,7 @@ func Disable(ctx context.Context, unit string, opts Options) error {
// manager configuration is reloaded (in a way equivalent to daemon-reload),
// in order to ensure the changes are taken into account immediately.
func Enable(ctx context.Context, unit string, opts Options) error {
args := []string{"enable", "--system", unit}
if opts.UserMode {
args[1] = "--user"
}
_, _, _, err := execute(ctx, args)
return err
return enable(ctx, unit, opts)
}
// Check whether any of the specified units are active (i.e. running).
@@ -71,24 +49,8 @@ func Enable(ctx context.Context, unit string, opts Options) error {
// Returns true if the unit is active, false if inactive or failed.
// Also returns false in an error case.
func IsActive(ctx context.Context, unit string, opts Options) (bool, error) {
args := []string{"is-active", "--system", unit}
if opts.UserMode {
args[1] = "--user"
}
stdout, _, _, err := execute(ctx, args)
stdout = strings.TrimSuffix(stdout, "\n")
switch stdout {
case "inactive":
return false, nil
case "active":
return true, nil
case "failed":
return false, nil
case "activating":
return false, nil
default:
return false, err
}
result, err := isActive(ctx, unit, opts)
return result, err
}
// Checks whether any of the specified unit files are enabled (as with enable).
@@ -101,59 +63,14 @@ func IsActive(ctx context.Context, unit string, opts Options) (bool, error) {
// See https://www.freedesktop.org/software/systemd/man/systemctl.html#is-enabled%20UNIT%E2%80%A6
// for more information
func IsEnabled(ctx context.Context, unit string, opts Options) (bool, error) {
args := []string{"is-enabled", "--system", unit}
if opts.UserMode {
args[1] = "--user"
}
stdout, _, _, err := execute(ctx, args)
stdout = strings.TrimSuffix(stdout, "\n")
switch stdout {
case "enabled":
return true, nil
case "enabled-runtime":
return true, nil
case "linked":
return false, ErrLinked
case "linked-runtime":
return false, ErrLinked
case "alias":
return true, nil
case "masked":
return false, ErrMasked
case "masked-runtime":
return false, ErrMasked
case "static":
return true, nil
case "indirect":
return true, nil
case "disabled":
return false, nil
case "generated":
return true, nil
case "transient":
return true, nil
}
if err != nil {
return false, err
}
return false, ErrUnspecified
result, err := isEnabled(ctx, unit, opts)
return result, err
}
// Check whether any of the specified units are in a "failed" state.
func IsFailed(ctx context.Context, unit string, opts Options) (bool, error) {
args := []string{"is-failed", "--system", unit}
if opts.UserMode {
args[1] = "--user"
}
stdout, _, _, err := execute(ctx, args)
if matched, _ := regexp.MatchString(`inactive`, stdout); matched {
return false, nil
} else if matched, _ := regexp.MatchString(`active`, stdout); matched {
return false, nil
} else if matched, _ := regexp.MatchString(`failed`, stdout); matched {
return true, nil
}
return false, err
result, err := isFailed(ctx, unit, opts)
return result, err
}
// Mask one or more units, as specified on the command line. This will link
@@ -163,70 +80,40 @@ func IsFailed(ctx context.Context, unit string, opts Options) (bool, error) {
// continue masking anyway. Calling Mask on a non-existing masked unit does not
// return an error. Similarly, see Unmask.
func Mask(ctx context.Context, unit string, opts Options) error {
args := []string{"mask", "--system", unit}
if opts.UserMode {
args[1] = "--user"
}
_, _, _, err := execute(ctx, args)
return err
return mask(ctx, unit, opts)
}
// Stop and then start one or more units specified on the command line.
// If the units are not running yet, they will be started.
func Restart(ctx context.Context, unit string, opts Options) error {
args := []string{"restart", "--system", unit}
if opts.UserMode {
args[1] = "--user"
}
_, _, _, err := execute(ctx, args)
return err
return restart(ctx, unit, opts)
}
// Show a selected property of a unit. Accepted properties are predefined in the
// properties subpackage to guarantee properties are valid and assist code-completion.
func Show(ctx context.Context, unit string, property properties.Property, opts Options) (string, error) {
args := []string{"show", "--system", unit, "--property", string(property)}
if opts.UserMode {
args[1] = "--user"
}
stdout, _, _, err := execute(ctx, args)
stdout = strings.TrimPrefix(stdout, string(property)+"=")
stdout = strings.TrimSuffix(stdout, "\n")
return stdout, err
str, err := show(ctx, unit, property, opts)
return str, err
}
// Start (activate) a given unit
func Start(ctx context.Context, unit string, opts Options) error {
args := []string{"start", "--system", unit}
if opts.UserMode {
args[1] = "--user"
}
_, _, _, err := execute(ctx, args)
return err
return start(ctx, unit, opts)
}
// 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}
if opts.UserMode {
args[1] = "--user"
}
stdout, _, _, err := execute(ctx, args)
return stdout, err
stat, err := status(ctx, unit, opts)
return stat, err
}
// Stop (deactivate) a given unit
func Stop(ctx context.Context, unit string, opts Options) error {
args := []string{"stop", "--system", unit}
if opts.UserMode {
args[1] = "--user"
}
_, _, _, err := execute(ctx, args)
return err
return stop(ctx, unit, opts)
}
// Unmask one or more unit files, as specified on the command line.
@@ -237,10 +124,5 @@ func Stop(ctx context.Context, unit string, opts Options) error {
// If the unit doesn't exist but it's masked anyway, no error will be
// returned. Gross, I know. Take it up with Poettering.
func Unmask(ctx context.Context, unit string, opts Options) error {
args := []string{"unmask", "--system", unit}
if opts.UserMode {
args[1] = "--user"
}
_, _, _, err := execute(ctx, args)
return err
return unmask(ctx, unit, opts)
}

65
systemctl_darwin.go Normal file
View File

@@ -0,0 +1,65 @@
//go:build !linux
package systemctl
import (
"context"
"github.com/taigrr/systemctl/properties"
)
func daemonReload(ctx context.Context, opts Options) error {
return nil
}
func reenable(ctx context.Context, unit string, opts Options) error {
return nil
}
func disable(ctx context.Context, unit string, opts Options) error {
return nil
}
func enable(ctx context.Context, unit string, opts Options) error {
return nil
}
func isActive(ctx context.Context, unit string, opts Options) (bool, error) {
return false, nil
}
func isEnabled(ctx context.Context, unit string, opts Options) (bool, error) {
return false, nil
}
func isFailed(ctx context.Context, unit string, opts Options) (bool, error) {
return false, nil
}
func mask(ctx context.Context, unit string, opts Options) error {
return nil
}
func restart(ctx context.Context, unit string, opts Options) error {
return nil
}
func show(ctx context.Context, unit string, property properties.Property, opts Options) (string, error) {
return "", nil
}
func start(ctx context.Context, unit string, opts Options) error {
return nil
}
func status(ctx context.Context, unit string, opts Options) (string, error) {
return "", nil
}
func stop(ctx context.Context, unit string, opts Options) error {
return nil
}
func unmask(ctx context.Context, unit string, opts Options) error {
return nil
}

250
systemctl_linux.go Normal file
View File

@@ -0,0 +1,250 @@
//go:build linux
package systemctl
import (
"context"
"strings"
"github.com/taigrr/systemctl/properties"
)
// Reload systemd manager configuration.
//
// This will rerun all generators (see systemd. generator(7)), reload all unit
// files, and recreate the entire dependency tree. While the daemon is being
// reloaded, all sockets systemd listens on behalf of user configuration will
// stay accessible.
func daemonReload(ctx context.Context, opts Options) error {
args := []string{"daemon-reload", "--system"}
if opts.UserMode {
args[1] = "--user"
}
_, _, _, err := execute(ctx, args)
return err
}
// Reenables one or more units.
//
// This removes all symlinks to the unit files backing the specified units from
// the unit configuration directory, then recreates the symlink to the unit again,
// atomically. Can be used to change the symlink target.
func reenable(ctx context.Context, unit string, opts Options) error {
args := []string{"reenable", "--system", unit}
if opts.UserMode {
args[1] = "--user"
}
_, _, _, err := execute(ctx, args)
return err
}
// Disables one or more units.
//
// This removes all symlinks to the unit files backing the specified units from
// the unit configuration directory, and hence undoes any changes made by
// enable or link.
func disable(ctx context.Context, unit string, opts Options) error {
args := []string{"disable", "--system", unit}
if opts.UserMode {
args[1] = "--user"
}
_, _, _, err := execute(ctx, args)
return err
}
// Enable one or more units or unit instances.
//
// This will create a set of symlinks, as encoded in the [Install] sections of
// the indicated unit files. After the symlinks have been created, the system
// manager configuration is reloaded (in a way equivalent to daemon-reload),
// in order to ensure the changes are taken into account immediately.
func enable(ctx context.Context, unit string, opts Options) error {
args := []string{"enable", "--system", unit}
if opts.UserMode {
args[1] = "--user"
}
_, _, _, err := execute(ctx, args)
return err
}
// Check whether any of the specified units are active (i.e. running).
//
// Returns true if the unit is active, false if inactive or failed.
// Also returns false in an error case.
func isActive(ctx context.Context, unit string, opts Options) (bool, error) {
args := []string{"is-active", "--system", unit}
if opts.UserMode {
args[1] = "--user"
}
stdout, _, _, err := execute(ctx, args)
stdout = strings.TrimSuffix(stdout, "\n")
switch stdout {
case "inactive":
return false, nil
case "active":
return true, nil
case "failed":
return false, nil
case "activating":
return false, nil
default:
return false, err
}
}
// Checks whether any of the specified unit files are enabled (as with enable).
//
// Returns true if the unit is enabled, aliased, static, indirect, generated
// or transient.
//
// Returns false if disabled. Also returns an error if linked, masked, or bad.
//
// See https://www.freedesktop.org/software/systemd/man/systemctl.html#is-enabled%20UNIT%E2%80%A6
// for more information
func isEnabled(ctx context.Context, unit string, opts Options) (bool, error) {
args := []string{"is-enabled", "--system", unit}
if opts.UserMode {
args[1] = "--user"
}
stdout, _, _, err := execute(ctx, args)
stdout = strings.TrimSuffix(stdout, "\n")
switch stdout {
case "enabled":
return true, nil
case "enabled-runtime":
return true, nil
case "linked":
return false, ErrLinked
case "linked-runtime":
return false, ErrLinked
case "alias":
return true, nil
case "masked":
return false, ErrMasked
case "masked-runtime":
return false, ErrMasked
case "static":
return true, nil
case "indirect":
return true, nil
case "disabled":
return false, nil
case "generated":
return true, nil
case "transient":
return true, nil
}
if err != nil {
return false, err
}
return false, ErrUnspecified
}
// Check whether any of the specified units are in a "failed" state.
func isFailed(ctx context.Context, unit string, opts Options) (bool, error) {
args := []string{"is-failed", "--system", unit}
if opts.UserMode {
args[1] = "--user"
}
stdout, _, _, err := execute(ctx, args)
stdout = strings.TrimSuffix(stdout, "\n")
switch stdout {
case "inactive":
return false, nil
case "active":
return false, nil
case "failed":
return true, nil
default:
return false, err
}
}
// Mask one or more units, as specified on the command line. This will link
// these unit files to /dev/null, making it impossible to start them.
//
// Notably, Mask may return ErrDoesNotExist if a unit doesn't exist, but it will
// continue masking anyway. Calling Mask on a non-existing masked unit does not
// return an error. Similarly, see Unmask.
func mask(ctx context.Context, unit string, opts Options) error {
args := []string{"mask", "--system", unit}
if opts.UserMode {
args[1] = "--user"
}
_, _, _, err := execute(ctx, args)
return err
}
// Stop and then start one or more units specified on the command line.
// If the units are not running yet, they will be started.
func restart(ctx context.Context, unit string, opts Options) error {
args := []string{"restart", "--system", unit}
if opts.UserMode {
args[1] = "--user"
}
_, _, _, err := execute(ctx, args)
return err
}
// Show a selected property of a unit. Accepted properties are predefined in the
// properties subpackage to guarantee properties are valid and assist code-completion.
func show(ctx context.Context, unit string, property properties.Property, opts Options) (string, error) {
args := []string{"show", "--system", unit, "--property", string(property)}
if opts.UserMode {
args[1] = "--user"
}
stdout, _, _, err := execute(ctx, args)
stdout = strings.TrimPrefix(stdout, string(property)+"=")
stdout = strings.TrimSuffix(stdout, "\n")
return stdout, err
}
// Start (activate) a given unit
func start(ctx context.Context, unit string, opts Options) error {
args := []string{"start", "--system", unit}
if opts.UserMode {
args[1] = "--user"
}
_, _, _, err := execute(ctx, args)
return err
}
// Get back the status string which would be returned by running
// `systemctl status [unit]`.
//
// 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}
if opts.UserMode {
args[1] = "--user"
}
stdout, _, _, err := execute(ctx, args)
return stdout, err
}
// Stop (deactivate) a given unit
func stop(ctx context.Context, unit string, opts Options) error {
args := []string{"stop", "--system", unit}
if opts.UserMode {
args[1] = "--user"
}
_, _, _, err := execute(ctx, args)
return err
}
// Unmask one or more unit files, as specified on the command line.
// This will undo the effect of Mask.
//
// In line with systemd, Unmask will return ErrDoesNotExist if the unit
// doesn't exist, but only if it's not already masked.
// If the unit doesn't exist but it's masked anyway, no error will be
// returned. Gross, I know. Take it up with Poettering.
func unmask(ctx context.Context, unit string, opts Options) error {
args := []string{"unmask", "--system", unit}
if opts.UserMode {
args[1] = "--user"
}
_, _, _, err := execute(ctx, args)
return err
}

View File

@@ -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