You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
87 lines
2.0 KiB
87 lines
2.0 KiB
|
2 months ago
|
// Package systemcontrol defines the privileged system-control boundary used by
|
||
|
|
// the unprivileged management API.
|
||
|
|
package systemcontrol
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"errors"
|
||
|
|
"fmt"
|
||
|
|
"os"
|
||
|
|
"os/exec"
|
||
|
|
"path/filepath"
|
||
|
|
"strings"
|
||
|
|
)
|
||
|
|
|
||
|
|
type Controller interface {
|
||
|
|
Reboot(context.Context) error
|
||
|
|
}
|
||
|
|
|
||
|
|
// ResetButtonReader exposes the physical settings-reset button state without
|
||
|
|
// granting the unprivileged API direct GPIO access.
|
||
|
|
type ResetButtonReader interface {
|
||
|
|
ResetButtonPressed(context.Context) (bool, error)
|
||
|
|
}
|
||
|
|
|
||
|
|
type SysfsResetButton struct {
|
||
|
|
ValuePath string
|
||
|
|
ActiveLow bool
|
||
|
|
}
|
||
|
|
|
||
|
|
func (b SysfsResetButton) ResetButtonPressed(ctx context.Context) (bool, error) {
|
||
|
|
if err := ctx.Err(); err != nil {
|
||
|
|
return false, err
|
||
|
|
}
|
||
|
|
path := strings.TrimSpace(b.ValuePath)
|
||
|
|
if path == "" || !filepath.IsAbs(path) {
|
||
|
|
return false, errors.New("reset button value path is not configured")
|
||
|
|
}
|
||
|
|
content, err := os.ReadFile(path)
|
||
|
|
if err != nil {
|
||
|
|
return false, fmt.Errorf("read reset button value: %w", err)
|
||
|
|
}
|
||
|
|
value := strings.TrimSpace(string(content))
|
||
|
|
if value != "0" && value != "1" {
|
||
|
|
return false, errors.New("reset button value must be 0 or 1")
|
||
|
|
}
|
||
|
|
pressed := value == "1"
|
||
|
|
if b.ActiveLow {
|
||
|
|
pressed = !pressed
|
||
|
|
}
|
||
|
|
return pressed, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
type CommandController struct {
|
||
|
|
RebootCommand string
|
||
|
|
}
|
||
|
|
|
||
|
|
func (c CommandController) Reboot(ctx context.Context) error {
|
||
|
|
command := strings.TrimSpace(c.RebootCommand)
|
||
|
|
if command == "" {
|
||
|
|
command = "/sbin/reboot"
|
||
|
|
}
|
||
|
|
if err := ctx.Err(); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
process := exec.Command(command)
|
||
|
|
if err := process.Start(); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
return process.Process.Release()
|
||
|
|
}
|
||
|
|
|
||
|
|
type MockController struct {
|
||
|
|
RebootError error
|
||
|
|
}
|
||
|
|
|
||
|
|
func (c MockController) Reboot(context.Context) error { return c.RebootError }
|
||
|
|
|
||
|
|
type UnavailableController struct{ Reason string }
|
||
|
|
|
||
|
|
func (c UnavailableController) Reboot(context.Context) error {
|
||
|
|
reason := strings.TrimSpace(c.Reason)
|
||
|
|
if reason == "" {
|
||
|
|
reason = "privileged system control is not configured"
|
||
|
|
}
|
||
|
|
return errors.New(reason)
|
||
|
|
}
|