diff --git a/README.md b/README.md index f70f698..4500bb0 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,6 @@ This library aims at providing idiomatic `systemctl` bindings for go developers, This tool tries to take guesswork out of arbitrarily shelling out to `systemctl` by providing a structured, thoroughly-tested wrapper for the `systemctl` functions most-likely to be used in a system program. If your system isn't running (or targeting another system running) `systemctl`, this library will be of little use to you. -In fact, if `systemctl` isn't found in the `PATH`, this library will panic. ## What is systemctl diff --git a/errors.go b/errors.go index f9de304..c97327b 100644 --- a/errors.go +++ b/errors.go @@ -21,7 +21,6 @@ var ( // Masked units can only be unmasked, but something else was attempted // Unmask the unit before enabling or disabling it ErrMasked = errors.New("unit masked") - // If this error occurs, the library isn't entirely useful, as it causes a panic // Make sure systemctl is in the PATH before calling again ErrNotInstalled = errors.New("systemctl not in $PATH") // A unit was expected to be running but was found inactive diff --git a/errors_test.go b/errors_test.go index 5211426..72d3e45 100644 --- a/errors_test.go +++ b/errors_test.go @@ -2,6 +2,7 @@ package systemctl import ( "context" + "errors" "fmt" "reflect" "runtime" @@ -63,7 +64,7 @@ func TestErrorFuncs(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) defer cancel() err := f(ctx, tc.unit, tc.opts) - if err != tc.err { + if !errors.Is(err, tc.err) { t.Errorf("error is %v, but should have been %v", err, tc.err) } }) diff --git a/go.mod b/go.mod index af05779..9e9a396 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module github.com/taigrr/systemctl -go 1.17 +go 1.12 diff --git a/helpers_test.go b/helpers_test.go index 6de91c8..d4b91e1 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -2,6 +2,7 @@ package systemctl import ( "context" + "errors" "fmt" "syscall" "testing" @@ -58,13 +59,13 @@ func TestGetStartTime(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) defer cancel() _, err := GetStartTime(ctx, tc.unit, tc.opts) - if err != tc.err { + if !errors.Is(err, tc.err) { t.Errorf("error is %v, but should have been %v", err, tc.err) } }) } // Prove start time changes after a restart - t.Run(fmt.Sprintf("prove start time changes"), func(t *testing.T) { + t.Run("prove start time changes", func(t *testing.T) { if userString != "root" && userString != "system" { t.Skip("skipping superuser test while running as user") } @@ -93,12 +94,13 @@ func TestGetStartTime(t *testing.T) { } func TestGetNumRestarts(t *testing.T) { - testCases := []struct { + type testCase struct { unit string err error opts Options runAsUser bool - }{ + } + testCases := []testCase{ // Run these tests only as a user // try nonexistant unit in user mode as user @@ -118,23 +120,25 @@ func TestGetNumRestarts(t *testing.T) { {"nginx", nil, Options{UserMode: false}, false}, } for _, tc := range testCases { - t.Run(fmt.Sprintf("%s as %s", tc.unit, userString), func(t *testing.T) { - t.Parallel() - if (userString == "root" || userString == "system") && tc.runAsUser { - t.Skip("skipping user test while running as superuser") - } else if (userString != "root" && userString != "system") && !tc.runAsUser { - t.Skip("skipping superuser test while running as user") - } - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) - defer cancel() - _, err := GetNumRestarts(ctx, tc.unit, tc.opts) - if err != tc.err { - t.Errorf("error is %v, but should have been %v", err, tc.err) - } - }) + func(tc testCase) { + t.Run(fmt.Sprintf("%s as %s", tc.unit, userString), func(t *testing.T) { + t.Parallel() + if (userString == "root" || userString == "system") && tc.runAsUser { + t.Skip("skipping user test while running as superuser") + } else if (userString != "root" && userString != "system") && !tc.runAsUser { + t.Skip("skipping superuser test while running as user") + } + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + _, err := GetNumRestarts(ctx, tc.unit, tc.opts) + if !errors.Is(err, tc.err) { + t.Errorf("error is %v, but should have been %v", err, tc.err) + } + }) + }(tc) } // Prove restart count increases by one after a restart - t.Run(fmt.Sprintf("prove restart count increases by one after a restart"), func(t *testing.T) { + t.Run("prove restart count increases by one after a restart", func(t *testing.T) { if testing.Short() { t.Skip("skipping in short mode") } @@ -154,9 +158,9 @@ func TestGetNumRestarts(t *testing.T) { } syscall.Kill(pid, syscall.SIGKILL) for { - running, err := IsActive(ctx, "nginx", Options{UserMode: false}) - if err != nil { - t.Errorf("error asserting nginx is up: %v", err) + running, errIsActive := IsActive(ctx, "nginx", Options{UserMode: false}) + if errIsActive != nil { + t.Errorf("error asserting nginx is up: %v", errIsActive) break } else if running { break @@ -173,12 +177,13 @@ func TestGetNumRestarts(t *testing.T) { } func TestGetMemoryUsage(t *testing.T) { - testCases := []struct { + type testCase struct { unit string err error opts Options runAsUser bool - }{ + } + testCases := []testCase{ // Run these tests only as a user // try nonexistant unit in user mode as user @@ -198,23 +203,25 @@ func TestGetMemoryUsage(t *testing.T) { {"nginx", nil, Options{UserMode: false}, false}, } for _, tc := range testCases { - t.Run(fmt.Sprintf("%s as %s", tc.unit, userString), func(t *testing.T) { - t.Parallel() - if (userString == "root" || userString == "system") && tc.runAsUser { - t.Skip("skipping user test while running as superuser") - } else if (userString != "root" && userString != "system") && !tc.runAsUser { - t.Skip("skipping superuser test while running as user") - } - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) - defer cancel() - _, err := GetMemoryUsage(ctx, tc.unit, tc.opts) - if err != tc.err { - t.Errorf("error is %v, but should have been %v", err, tc.err) - } - }) + func(tc testCase) { + t.Run(fmt.Sprintf("%s as %s", tc.unit, userString), func(t *testing.T) { + t.Parallel() + if (userString == "root" || userString == "system") && tc.runAsUser { + t.Skip("skipping user test while running as superuser") + } else if (userString != "root" && userString != "system") && !tc.runAsUser { + t.Skip("skipping superuser test while running as user") + } + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + _, err := GetMemoryUsage(ctx, tc.unit, tc.opts) + if !errors.Is(err, tc.err) { + t.Errorf("error is %v, but should have been %v", err, tc.err) + } + }) + }(tc) } // Prove memory usage values change across services - t.Run(fmt.Sprintf("prove memory usage values change across services"), func(t *testing.T) { + t.Run("prove memory usage values change across services", func(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) defer cancel() bytes, err := GetMemoryUsage(ctx, "nginx", Options{UserMode: false}) @@ -232,12 +239,14 @@ func TestGetMemoryUsage(t *testing.T) { } func TestGetPID(t *testing.T) { - testCases := []struct { + type testCase struct { unit string err error opts Options runAsUser bool - }{ + } + + testCases := []testCase{ // Run these tests only as a user // try nonexistant unit in user mode as user @@ -257,22 +266,24 @@ func TestGetPID(t *testing.T) { {"nginx", nil, Options{UserMode: false}, false}, } for _, tc := range testCases { - t.Run(fmt.Sprintf("%s as %s", tc.unit, userString), func(t *testing.T) { - t.Parallel() - if (userString == "root" || userString == "system") && tc.runAsUser { - t.Skip("skipping user test while running as superuser") - } else if (userString != "root" && userString != "system") && !tc.runAsUser { - t.Skip("skipping superuser test while running as user") - } - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) - defer cancel() - _, err := GetPID(ctx, tc.unit, tc.opts) - if err != tc.err { - t.Errorf("error is %v, but should have been %v", err, tc.err) - } - }) + func(tc testCase) { + t.Run(fmt.Sprintf("%s as %s", tc.unit, userString), func(t *testing.T) { + t.Parallel() + if (userString == "root" || userString == "system") && tc.runAsUser { + t.Skip("skipping user test while running as superuser") + } else if (userString != "root" && userString != "system") && !tc.runAsUser { + t.Skip("skipping superuser test while running as user") + } + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + _, err := GetPID(ctx, tc.unit, tc.opts) + if !errors.Is(err, tc.err) { + t.Errorf("error is %v, but should have been %v", err, tc.err) + } + }) + }(tc) } - t.Run(fmt.Sprintf("prove pid changes"), func(t *testing.T) { + t.Run("prove pid changes", func(t *testing.T) { if testing.Short() { t.Skip("skipping in short mode") } diff --git a/systemctl_test.go b/systemctl_test.go index 94a4f3c..6e8ef5d 100644 --- a/systemctl_test.go +++ b/systemctl_test.go @@ -2,6 +2,7 @@ package systemctl import ( "context" + "errors" "fmt" "os" "os/user" @@ -73,7 +74,7 @@ func TestDaemonReload(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) defer cancel() err := DaemonReload(ctx, tc.opts) - if err != tc.err { + if !errors.Is(err, tc.err) { t.Errorf("error is %v, but should have been %v", err, tc.err) } }) @@ -81,78 +82,72 @@ func TestDaemonReload(t *testing.T) { } func TestDisable(t *testing.T) { - t.Run(fmt.Sprintf(""), func(t *testing.T) { - if userString != "root" && userString != "system" { - t.Skip("skipping superuser test while running as user") - } - unit := "nginx" - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - err := Mask(ctx, unit, Options{UserMode: false}) - if err != nil { - Unmask(ctx, unit, Options{UserMode: false}) - t.Errorf("Unable to mask %s", unit) - } - err = Disable(ctx, unit, Options{UserMode: false}) - if err != ErrMasked { - Unmask(ctx, unit, Options{UserMode: false}) - t.Errorf("error is %v, but should have been %v", err, ErrMasked) - } - err = Unmask(ctx, unit, Options{UserMode: false}) - if err != nil { - t.Errorf("Unable to unmask %s", unit) - } - }) + if userString != "root" && userString != "system" { + t.Skip("skipping superuser test while running as user") + } + unit := "nginx" + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + err := Mask(ctx, unit, Options{UserMode: false}) + if err != nil { + Unmask(ctx, unit, Options{UserMode: false}) + t.Errorf("Unable to mask %s", unit) + } + err = Disable(ctx, unit, Options{UserMode: false}) + if !errors.Is(err, ErrMasked) { + Unmask(ctx, unit, Options{UserMode: false}) + t.Errorf("error is %v, but should have been %v", err, ErrMasked) + } + err = Unmask(ctx, unit, Options{UserMode: false}) + if err != nil { + t.Errorf("Unable to unmask %s", unit) + } } func TestReenable(t *testing.T) { - t.Run(fmt.Sprintf(""), func(t *testing.T) { - if userString != "root" && userString != "system" { - t.Skip("skipping superuser test while running as user") - } - unit := "nginx" - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - err := Mask(ctx, unit, Options{UserMode: false}) - if err != nil { - Unmask(ctx, unit, Options{UserMode: false}) - t.Errorf("Unable to mask %s", unit) - } - err = Reenable(ctx, unit, Options{UserMode: false}) - if err != ErrMasked { - Unmask(ctx, unit, Options{UserMode: false}) - t.Errorf("error is %v, but should have been %v", err, ErrMasked) - } - err = Unmask(ctx, unit, Options{UserMode: false}) - if err != nil { - t.Errorf("Unable to unmask %s", unit) - } - }) + if userString != "root" && userString != "system" { + t.Skip("skipping superuser test while running as user") + } + unit := "nginx" + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + err := Mask(ctx, unit, Options{UserMode: false}) + if err != nil { + Unmask(ctx, unit, Options{UserMode: false}) + t.Errorf("Unable to mask %s", unit) + } + err = Reenable(ctx, unit, Options{UserMode: false}) + if !errors.Is(err, ErrMasked) { + Unmask(ctx, unit, Options{UserMode: false}) + t.Errorf("error is %v, but should have been %v", err, ErrMasked) + } + err = Unmask(ctx, unit, Options{UserMode: false}) + if err != nil { + t.Errorf("Unable to unmask %s", unit) + } } func TestEnable(t *testing.T) { - t.Run(fmt.Sprintf(""), func(t *testing.T) { - if userString != "root" && userString != "system" { - t.Skip("skipping superuser test while running as user") - } - unit := "nginx" - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - err := Mask(ctx, unit, Options{UserMode: false}) - if err != nil { - Unmask(ctx, unit, Options{UserMode: false}) - t.Errorf("Unable to mask %s", unit) - } - err = Enable(ctx, unit, Options{UserMode: false}) - if err != ErrMasked { - Unmask(ctx, unit, Options{UserMode: false}) - t.Errorf("error is %v, but should have been %v", err, ErrMasked) - } - err = Unmask(ctx, unit, Options{UserMode: false}) - if err != nil { - t.Errorf("Unable to unmask %s", unit) - } - }) + if userString != "root" && userString != "system" { + t.Skip("skipping superuser test while running as user") + } + unit := "nginx" + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + err := Mask(ctx, unit, Options{UserMode: false}) + if err != nil { + Unmask(ctx, unit, Options{UserMode: false}) + t.Errorf("Unable to mask %s", unit) + } + err = Enable(ctx, unit, Options{UserMode: false}) + if !errors.Is(err, ErrMasked) { + Unmask(ctx, unit, Options{UserMode: false}) + t.Errorf("error is %v, but should have been %v", err, ErrMasked) + } + err = Unmask(ctx, unit, Options{UserMode: false}) + if err != nil { + t.Errorf("Unable to unmask %s", unit) + } } func ExampleEnable() { @@ -161,16 +156,16 @@ func ExampleEnable() { defer cancel() err := Enable(ctx, unit, Options{UserMode: true}) - switch err { - case ErrMasked: + switch { + case errors.Is(err, ErrMasked): fmt.Printf("%s is masked, unmask it before enabling\n", unit) - case ErrDoesNotExist: + case errors.Is(err, ErrDoesNotExist): fmt.Printf("%s does not exist\n", unit) - case ErrInsufficientPermissions: + case errors.Is(err, ErrInsufficientPermissions): fmt.Printf("permission to enable %s denied\n", unit) - case ErrBusFailure: + case errors.Is(err, ErrBusFailure): fmt.Printf("Cannot communicate with the bus\n") - case nil: + case err == nil: fmt.Printf("%s enabled successfully\n", unit) default: fmt.Printf("Error: %v", err) @@ -179,7 +174,7 @@ func ExampleEnable() { func TestIsActive(t *testing.T) { unit := "nginx" - t.Run(fmt.Sprintf("check active"), func(t *testing.T) { + t.Run("check active", func(t *testing.T) { if testing.Short() { t.Skip("skipping in short mode") } @@ -195,10 +190,10 @@ func TestIsActive(t *testing.T) { time.Sleep(time.Second) isActive, err := IsActive(ctx, unit, Options{UserMode: false}) if !isActive { - t.Errorf("IsActive didn't return true for %s", unit) + t.Errorf("IsActive didn't return true for %s: %v", unit, err) } }) - t.Run(fmt.Sprintf("check masked"), func(t *testing.T) { + t.Run("check masked", func(t *testing.T) { if userString != "root" && userString != "system" { t.Skip("skipping superuser test while running as user") } @@ -214,7 +209,7 @@ func TestIsActive(t *testing.T) { } Unmask(ctx, unit, Options{UserMode: false}) }) - t.Run(fmt.Sprintf("check masked"), func(t *testing.T) { + t.Run("check masked", func(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) defer cancel() _, err := IsActive(ctx, "nonexistant", Options{UserMode: false}) @@ -231,7 +226,7 @@ func TestIsEnabled(t *testing.T) { userMode = true unit = "syncthing" } - t.Run(fmt.Sprintf("check enabled"), func(t *testing.T) { + t.Run("check enabled", func(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() err := Enable(ctx, unit, Options{UserMode: userMode}) @@ -240,10 +235,10 @@ func TestIsEnabled(t *testing.T) { } isEnabled, err := IsEnabled(ctx, unit, Options{UserMode: userMode}) if !isEnabled { - t.Errorf("IsEnabled didn't return true for %s", unit) + t.Errorf("IsEnabled didn't return true for %s: %v", unit, err) } }) - t.Run(fmt.Sprintf("check disabled"), func(t *testing.T) { + t.Run("check disabled", func(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) defer cancel() err := Disable(ctx, unit, Options{UserMode: userMode}) @@ -259,7 +254,7 @@ func TestIsEnabled(t *testing.T) { } Enable(ctx, unit, Options{UserMode: false}) }) - t.Run(fmt.Sprintf("check masked"), func(t *testing.T) { + t.Run("check masked", func(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) defer cancel() err := Mask(ctx, unit, Options{UserMode: userMode}) @@ -320,13 +315,13 @@ func TestMask(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) defer cancel() err := Mask(ctx, tc.unit, tc.opts) - if err != tc.err { + if !errors.Is(err, tc.err) { t.Errorf("error is %v, but should have been %v", err, tc.err) } Unmask(ctx, tc.unit, tc.opts) }) } - t.Run(fmt.Sprintf("test double masking existing"), func(t *testing.T) { + t.Run("test double masking existing", func(t *testing.T) { unit := "nginx" userMode := false if userString != "root" && userString != "system" { @@ -346,17 +341,15 @@ func TestMask(t *testing.T) { } Unmask(ctx, unit, opts) }) - t.Run(fmt.Sprintf("test double masking nonexisting"), func(t *testing.T) { + t.Run("test double masking nonexisting", func(t *testing.T) { unit := "nonexistant" - userMode := false - if userString != "root" && userString != "system" { - userMode = true - } + userMode := userString != "root" && userString != "system" + opts := Options{UserMode: userMode} ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) defer cancel() err := Mask(ctx, unit, opts) - if err != ErrDoesNotExist { + if !errors.Is(err, ErrDoesNotExist) { t.Errorf("error on initial masking is %v, but should have been %v", err, ErrDoesNotExist) } err = Mask(ctx, unit, opts) @@ -388,9 +381,9 @@ func TestRestart(t *testing.T) { } syscall.Kill(pid, syscall.SIGKILL) for { - running, err := IsActive(ctx, unit, opts) - if err != nil { - t.Errorf("error asserting %s is up: %v", unit, err) + running, errIsActive := IsActive(ctx, unit, opts) + if errIsActive != nil { + t.Errorf("error asserting %s is up: %v", unit, errIsActive) break } else if running { break @@ -415,15 +408,17 @@ func TestShow(t *testing.T) { UserMode: false, } for _, x := range properties.Properties { - t.Run(fmt.Sprintf("show property %s", string(x)), func(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) - defer cancel() - t.Parallel() - _, err := Show(ctx, unit, x, opts) - if err != nil { - t.Errorf("error is %v, but should have been %v", err, nil) - } - }) + func(x properties.Property) { + t.Run(fmt.Sprintf("show property %s", string(x)), func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + t.Parallel() + _, err := Show(ctx, unit, x, opts) + if err != nil { + t.Errorf("error is %v, but should have been %v", err, nil) + } + }) + }(x) } } @@ -551,13 +546,13 @@ func TestUnmask(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) defer cancel() err := Mask(ctx, tc.unit, tc.opts) - if err != tc.err { + if !errors.Is(err, tc.err) { t.Errorf("error is %v, but should have been %v", err, tc.err) } Unmask(ctx, tc.unit, tc.opts) }) } - t.Run(fmt.Sprintf("test double unmasking existing"), func(t *testing.T) { + t.Run("test double unmasking existing", func(t *testing.T) { unit := "nginx" userMode := false if userString != "root" && userString != "system" { @@ -577,12 +572,10 @@ func TestUnmask(t *testing.T) { } Unmask(ctx, unit, opts) }) - t.Run(fmt.Sprintf("test double unmasking nonexisting"), func(t *testing.T) { + t.Run("test double unmasking nonexisting", func(t *testing.T) { unit := "nonexistant" - userMode := false - if userString != "root" && userString != "system" { - userMode = true - } + userMode := userString != "root" && userString != "system" + opts := Options{UserMode: userMode} ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) defer cancel() @@ -592,7 +585,7 @@ func TestUnmask(t *testing.T) { t.Errorf("error on initial unmasking is %v, but should have been %v", err, nil) } err = Unmask(ctx, unit, opts) - if err != ErrDoesNotExist { + if !errors.Is(err, ErrDoesNotExist) { t.Errorf("error on second unmasking is %v, but should have been %v", err, ErrDoesNotExist) } }) diff --git a/util.go b/util.go index a132d96..f66205f 100644 --- a/util.go +++ b/util.go @@ -3,10 +3,10 @@ package systemctl import ( "bytes" "context" + "errors" "fmt" - "log" "os/exec" - "regexp" + "strings" ) var systemctl string @@ -14,12 +14,7 @@ var systemctl string const killed = 130 func init() { - path, err := exec.LookPath("systemctl") - if err != nil { - log.Printf("%v", ErrNotInstalled) - systemctl = "" - return - } + path, _ := exec.LookPath("systemctl") systemctl = path } @@ -34,7 +29,7 @@ func execute(ctx context.Context, args []string) (string, string, int, error) { ) if systemctl == "" { - panic(ErrNotInstalled) + return "", "", 1, ErrNotInstalled } cmd := exec.CommandContext(ctx, systemctl, args...) cmd.Stdout = &stdout @@ -56,32 +51,26 @@ func execute(ctx context.Context, args []string) (string, string, int, error) { } func filterErr(stderr string) error { - if matched, _ := regexp.MatchString(`does not exist`, stderr); matched { - return ErrDoesNotExist + switch { + case strings.Contains(`does not exist`, stderr): + return errors.Join(ErrDoesNotExist, fmt.Errorf("stderr: %s", stderr)) + case strings.Contains(`not found.`, stderr): + return errors.Join(ErrDoesNotExist, fmt.Errorf("stderr: %s", stderr)) + case strings.Contains(`not loaded.`, stderr): + return errors.Join(ErrUnitNotLoaded, fmt.Errorf("stderr: %s", stderr)) + case strings.Contains(`No such file or directory`, stderr): + return errors.Join(ErrDoesNotExist, fmt.Errorf("stderr: %s", stderr)) + case strings.Contains(`Interactive authentication required`, stderr): + return errors.Join(ErrInsufficientPermissions, fmt.Errorf("stderr: %s", stderr)) + case strings.Contains(`Access denied`, stderr): + return errors.Join(ErrInsufficientPermissions, fmt.Errorf("stderr: %s", stderr)) + case strings.Contains(`DBUS_SESSION_BUS_ADDRESS`, stderr): + return errors.Join(ErrBusFailure, fmt.Errorf("stderr: %s", stderr)) + case strings.Contains(`is masked`, stderr): + return errors.Join(ErrMasked, fmt.Errorf("stderr: %s", stderr)) + case strings.Contains(`Failed`, stderr): + return errors.Join(ErrUnspecified, fmt.Errorf("stderr: %s", stderr)) + default: + return nil } - if matched, _ := regexp.MatchString(`not found.`, stderr); matched { - return ErrDoesNotExist - } - if matched, _ := regexp.MatchString(`not loaded.`, stderr); matched { - return ErrUnitNotLoaded - } - if matched, _ := regexp.MatchString(`No such file or directory`, stderr); matched { - return ErrDoesNotExist - } - if matched, _ := regexp.MatchString(`Interactive authentication required`, stderr); matched { - return ErrInsufficientPermissions - } - if matched, _ := regexp.MatchString(`Access denied`, stderr); matched { - return ErrInsufficientPermissions - } - if matched, _ := regexp.MatchString(`DBUS_SESSION_BUS_ADDRESS`, stderr); matched { - return ErrBusFailure - } - if matched, _ := regexp.MatchString(`is masked`, stderr); matched { - return ErrMasked - } - if matched, _ := regexp.MatchString(`Failed`, stderr); matched { - return ErrUnspecified - } - return nil }