parent
b7c9a19f6a
commit
3edb64f1ed
@ -0,0 +1,74 @@ |
|||||||
|
package main |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"log/slog" |
||||||
|
"os" |
||||||
|
|
||||||
|
"github.com/spf13/cobra" |
||||||
|
|
||||||
|
"code.tokarch.uk/mainnika/acpi-wakeup-fixxer/pkg/wakeup" |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
devicesToDisableFlag []string |
||||||
|
) |
||||||
|
|
||||||
|
var rootCmd = &cobra.Command{ |
||||||
|
Use: "acpi-wakeup-fixxer", |
||||||
|
Short: "A CLI tool to fix ACPI wakeup issues", |
||||||
|
Args: cobra.NoArgs, |
||||||
|
Long: "acpi-wakeup-fixxer is a command-line tool to fix ACPI wakeup issues on your system.", |
||||||
|
Run: rootCmdRun, |
||||||
|
} |
||||||
|
|
||||||
|
func init() { |
||||||
|
rootCmd.PersistentFlags().StringArrayVar(&devicesToDisableFlag, "disable", []string{"LID0", "XHC1"}, "Devices to disable") |
||||||
|
} |
||||||
|
|
||||||
|
func main() { |
||||||
|
if err := rootCmd.Execute(); err != nil { |
||||||
|
fmt.Println(err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func rootCmdRun(cmd *cobra.Command, args []string) { |
||||||
|
wakeUpController := wakeup.NewWakeupController() |
||||||
|
|
||||||
|
devices, err := wakeUpController.GetWakeupDevices(wakeup.StatusEnabled) |
||||||
|
if err != nil { |
||||||
|
slog.Error("failed to get wakeup devices", "error", err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
|
||||||
|
slog.Info("Found devices", "devices", devices) |
||||||
|
slog.Info("Disabling devices", "devices", devicesToDisableFlag) |
||||||
|
|
||||||
|
var disabledDevices []string |
||||||
|
for _, device := range devices { |
||||||
|
needDisable := false |
||||||
|
for _, deviceToDisable := range devicesToDisableFlag { |
||||||
|
if device == deviceToDisable { |
||||||
|
needDisable = true |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if needDisable { |
||||||
|
slog.Info("Disabling device", "device", device) |
||||||
|
if err := wakeUpController.ToggleWakeupDevice(device); err != nil { |
||||||
|
slog.Error("failed to disable device", "device", device, "error", err) |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
disabledDevices = append(disabledDevices, device) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if len(disabledDevices) > 0 { |
||||||
|
slog.Info("Disabled devices", "devices", disabledDevices) |
||||||
|
} else { |
||||||
|
slog.Info("No devices were disabled") |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,16 @@ |
|||||||
|
module code.tokarch.uk/mainnika/acpi-wakeup-fixxer |
||||||
|
|
||||||
|
go 1.22.2 |
||||||
|
|
||||||
|
require ( |
||||||
|
github.com/spf13/cobra v1.8.0 |
||||||
|
github.com/stretchr/testify v1.9.0 |
||||||
|
) |
||||||
|
|
||||||
|
require ( |
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect |
||||||
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect |
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect |
||||||
|
github.com/spf13/pflag v1.0.5 // indirect |
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect |
||||||
|
) |
@ -0,0 +1,18 @@ |
|||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= |
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= |
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
||||||
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= |
||||||
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= |
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= |
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= |
||||||
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= |
||||||
|
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= |
||||||
|
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= |
||||||
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= |
||||||
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= |
||||||
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= |
||||||
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= |
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= |
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= |
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= |
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
@ -0,0 +1,36 @@ |
|||||||
|
package procfs |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"io" |
||||||
|
"os" |
||||||
|
) |
||||||
|
|
||||||
|
const ( |
||||||
|
acpiWakeupDefaultPath = "/proc/acpi/wakeup" |
||||||
|
) |
||||||
|
|
||||||
|
type Procfs interface { |
||||||
|
ACPIWakeup() (io.ReadCloser, error) |
||||||
|
ACPIWakeupWrite() (io.WriteCloser, error) |
||||||
|
} |
||||||
|
|
||||||
|
type ProcfsDefaultPath struct{} |
||||||
|
|
||||||
|
func (p *ProcfsDefaultPath) ACPIWakeup() (io.ReadCloser, error) { |
||||||
|
f, err := os.Open(acpiWakeupDefaultPath) |
||||||
|
if err != nil { |
||||||
|
return nil, fmt.Errorf("failed to open %s: %w", acpiWakeupDefaultPath, err) |
||||||
|
} |
||||||
|
|
||||||
|
return f, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (p *ProcfsDefaultPath) ACPIWakeupWrite() (io.WriteCloser, error) { |
||||||
|
f, err := os.OpenFile(acpiWakeupDefaultPath, os.O_WRONLY, 0) |
||||||
|
if err != nil { |
||||||
|
return nil, fmt.Errorf("failed to open for writing %s: %w", acpiWakeupDefaultPath, err) |
||||||
|
} |
||||||
|
|
||||||
|
return f, nil |
||||||
|
} |
@ -0,0 +1,88 @@ |
|||||||
|
package wakeup |
||||||
|
|
||||||
|
import ( |
||||||
|
"bufio" |
||||||
|
"fmt" |
||||||
|
"log/slog" |
||||||
|
"strings" |
||||||
|
|
||||||
|
"code.tokarch.uk/mainnika/acpi-wakeup-fixxer/pkg/procfs" |
||||||
|
) |
||||||
|
|
||||||
|
const ( |
||||||
|
deviceHeader = "Device" |
||||||
|
columnDeviceIndex = 0 |
||||||
|
columnStatusIndex = 2 |
||||||
|
columnIndexMax = 3 |
||||||
|
) |
||||||
|
|
||||||
|
type Status string |
||||||
|
|
||||||
|
const StatusAll Status = "" |
||||||
|
const StatusEnabled Status = "*enabled" |
||||||
|
const StatusDisabled Status = "*disabled" |
||||||
|
|
||||||
|
type WakeupController struct { |
||||||
|
ProcfsProvider procfs.Procfs |
||||||
|
} |
||||||
|
|
||||||
|
func NewWakeupController() *WakeupController { |
||||||
|
return &WakeupController{ProcfsProvider: &procfs.ProcfsDefaultPath{}} |
||||||
|
} |
||||||
|
|
||||||
|
func (w *WakeupController) GetWakeupDevices(withStatus Status) ([]string, error) { |
||||||
|
wakeupFile, err := w.ProcfsProvider.ACPIWakeup() |
||||||
|
if err != nil { |
||||||
|
return nil, fmt.Errorf("failed to get wakeup file: %w", err) |
||||||
|
} |
||||||
|
defer func() { _ = wakeupFile.Close() }() |
||||||
|
|
||||||
|
scanner := bufio.NewScanner(wakeupFile) |
||||||
|
var devices []string |
||||||
|
|
||||||
|
for scanner.Scan() { |
||||||
|
line := scanner.Text() |
||||||
|
fields := strings.Fields(line) |
||||||
|
if len(fields) < columnIndexMax { |
||||||
|
slog.Warn("unexpected number of columns", "line", line) |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
device := fields[columnDeviceIndex] |
||||||
|
if device == deviceHeader { |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
status := Status(fields[columnStatusIndex]) |
||||||
|
if withStatus != StatusAll && status != withStatus { |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
devices = append(devices, device) |
||||||
|
} |
||||||
|
if err := scanner.Err(); err != nil { |
||||||
|
return nil, fmt.Errorf("failed to scan wakeup file: %w", err) |
||||||
|
} |
||||||
|
|
||||||
|
return devices, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (w *WakeupController) ToggleWakeupDevice(device string) error { |
||||||
|
wakeupFile, err := w.ProcfsProvider.ACPIWakeupWrite() |
||||||
|
if err != nil { |
||||||
|
return fmt.Errorf("failed to open wakeup file for writing: %w", err) |
||||||
|
} |
||||||
|
|
||||||
|
_, err = fmt.Fprintf(wakeupFile, "%s\n", device) |
||||||
|
if err != nil { |
||||||
|
_ = wakeupFile.Close() |
||||||
|
return fmt.Errorf("failed to write to wakeup file: %w", err) |
||||||
|
} |
||||||
|
|
||||||
|
err = wakeupFile.Close() |
||||||
|
if err != nil { |
||||||
|
return fmt.Errorf("failed to close wakeup file: %w", err) |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
@ -0,0 +1,105 @@ |
|||||||
|
package wakeup |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"io" |
||||||
|
"strings" |
||||||
|
"testing" |
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert" |
||||||
|
) |
||||||
|
|
||||||
|
type procfsMock struct{} |
||||||
|
|
||||||
|
func (p *procfsMock) ACPIWakeup() (io.ReadCloser, error) { |
||||||
|
const testFileRaw = `Device S-state Status Sysfs node |
||||||
|
PEG0 S3 *disabled |
||||||
|
EC S4 *disabled platform:PNP0C09:00 |
||||||
|
HDEF S3 *disabled pci:0000:00:1b.0 |
||||||
|
RP01 S3 *enabled pci:0000:00:1c.0 |
||||||
|
RP02 S3 *enabled pci:0000:00:1c.1 |
||||||
|
RP03 S3 *enabled pci:0000:00:1c.2 |
||||||
|
ARPT S4 *enabled pci:0000:03:00.0 |
||||||
|
RP05 S3 *enabled pci:0000:00:1c.4 |
||||||
|
RP06 S3 *enabled pci:0000:00:1c.5 |
||||||
|
SPIT S3 *disabled spi:spi-APP000D:00 |
||||||
|
XHC1 S3 *disabled pci:0000:00:14.0 |
||||||
|
ADP1 S4 *disabled platform:ACPI0003:00 |
||||||
|
LID0 S4 *disabled platform:PNP0C0D:00 |
||||||
|
` |
||||||
|
|
||||||
|
return io.NopCloser(strings.NewReader(testFileRaw)), nil |
||||||
|
} |
||||||
|
|
||||||
|
func (p *procfsMock) ACPIWakeupWrite() (io.WriteCloser, error) { |
||||||
|
return nil, fmt.Errorf("not implemented") |
||||||
|
} |
||||||
|
|
||||||
|
func TestSomething(t *testing.T) { |
||||||
|
|
||||||
|
tt := []struct { |
||||||
|
name string |
||||||
|
wantStatus Status |
||||||
|
wantDevices []string |
||||||
|
}{ |
||||||
|
{ |
||||||
|
name: "Read and parse successfully", |
||||||
|
wantStatus: StatusEnabled, |
||||||
|
wantDevices: []string{ |
||||||
|
"RP01", |
||||||
|
"RP02", |
||||||
|
"RP03", |
||||||
|
"RP05", |
||||||
|
"RP06", |
||||||
|
"ARPT", |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "Read and parse disabled successfully", |
||||||
|
wantStatus: StatusDisabled, |
||||||
|
wantDevices: []string{ |
||||||
|
"PEG0", |
||||||
|
"EC", |
||||||
|
"HDEF", |
||||||
|
"SPIT", |
||||||
|
"XHC1", |
||||||
|
"ADP1", |
||||||
|
"LID0", |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "Read and parse all successfully", |
||||||
|
wantStatus: StatusAll, |
||||||
|
wantDevices: []string{ |
||||||
|
"RP01", |
||||||
|
"RP02", |
||||||
|
"RP03", |
||||||
|
"RP05", |
||||||
|
"RP06", |
||||||
|
"ARPT", |
||||||
|
"PEG0", |
||||||
|
"EC", |
||||||
|
"HDEF", |
||||||
|
"SPIT", |
||||||
|
"XHC1", |
||||||
|
"ADP1", |
||||||
|
"LID0", |
||||||
|
}, |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
for _, tc := range tt { |
||||||
|
t.Run(tc.name, func(t *testing.T) { |
||||||
|
c := NewWakeupController() |
||||||
|
c.ProcfsProvider = &procfsMock{} |
||||||
|
|
||||||
|
devices, err := c.GetWakeupDevices(tc.wantStatus) |
||||||
|
assert.NoError(t, err) |
||||||
|
|
||||||
|
assert.Len(t, devices, len(tc.wantDevices)) |
||||||
|
for i, wantDevice := range tc.wantDevices { |
||||||
|
assert.Contains(t, devices, wantDevice, "device %d not found", i) |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue