package systemd import ( "bufio" "bytes" "context" "errors" "fmt" "os/exec" "path/filepath" "strings" ) const ( // loadStateProperty systemctl show 输出的加载状态属性名。 loadStateProperty = "LoadState" // activeStateProperty systemctl show 输出的活动状态属性名。 activeStateProperty = "ActiveState" // subStateProperty systemctl show 输出的子状态属性名。 subStateProperty = "SubState" // loadedState 表示单元已加载。 loadedState = "loaded" // notFoundState 表示单元未找到。 notFoundState = "not-found" ) // Systemctl 直接调用一个精确的 systemctl 可执行文件,绝不经过 shell。 type Systemctl struct { // executable systemctl 的绝对路径。 executable string } // NewSystemctl 要求传入本地守护进程配置提供的可执行文件绝对路径。 func NewSystemctl(executable string) (*Systemctl, error) { if !filepath.IsAbs(executable) { return nil, errors.New("systemctl executable path must be absolute") } return &Systemctl{executable: executable}, nil } // Inspect 查询指定单元的关键状态属性并返回其状态。 // 未找到单元时错误包装 ErrUnitNotFound;加载状态异常时返回错误。 func (s *Systemctl) Inspect(ctx context.Context, unitName string) (Unit, error) { if err := validateUnitName(unitName); err != nil { return Unit{}, err } command := exec.CommandContext(ctx, s.executable, "show", "--no-pager", "--property="+loadStateProperty, "--property="+activeStateProperty, "--property="+subStateProperty, "--", unitName, ) output, err := command.CombinedOutput() if err != nil { return Unit{}, commandError("inspect systemd unit "+unitName, output, err) } unit, err := parseUnitProperties(unitName, output) if err != nil { return Unit{}, err } if unit.LoadState == notFoundState { return Unit{}, fmt.Errorf("inspect systemd unit %s: %w", unitName, ErrUnitNotFound) } if unit.LoadState != loadedState { return Unit{}, fmt.Errorf("systemd unit %s has unsupported load state %q", unitName, unit.LoadState) } return unit, nil } // Start 启动指定单元。 func (s *Systemctl) Start(ctx context.Context, unitName string) error { return s.changeState(ctx, "start", unitName) } // Stop 停止指定单元。 func (s *Systemctl) Stop(ctx context.Context, unitName string) error { return s.changeState(ctx, "stop", unitName) } // changeState 通过 systemctl 执行给定动作(start 或 stop)。 func (s *Systemctl) changeState(ctx context.Context, action, unitName string) error { if err := validateUnitName(unitName); err != nil { return err } output, err := exec.CommandContext(ctx, s.executable, action, "--", unitName).CombinedOutput() if err != nil { return commandError(action+" systemd unit "+unitName, output, err) } return nil } // parseUnitProperties 解析 systemctl show 的输出, // 严格要求恰好包含三个预期属性且不重复、不多余。 func parseUnitProperties(unitName string, output []byte) (Unit, error) { values := make(map[string]string, 3) scanner := bufio.NewScanner(bytes.NewReader(output)) for scanner.Scan() { line := scanner.Text() key, value, found := strings.Cut(line, "=") if !found { return Unit{}, fmt.Errorf("decode systemd unit %s property line %q", unitName, line) } switch key { case loadStateProperty, activeStateProperty, subStateProperty: if _, duplicate := values[key]; duplicate { return Unit{}, fmt.Errorf("decode systemd unit %s duplicate property %s", unitName, key) } values[key] = value default: return Unit{}, fmt.Errorf("decode systemd unit %s unexpected property %q", unitName, key) } } if err := scanner.Err(); err != nil { return Unit{}, fmt.Errorf("decode systemd unit %s properties: %w", unitName, err) } for _, key := range []string{loadStateProperty, activeStateProperty, subStateProperty} { if _, found := values[key]; !found { return Unit{}, fmt.Errorf("decode systemd unit %s missing property %s", unitName, key) } } return Unit{ Name: unitName, LoadState: values[loadStateProperty], ActiveState: values[activeStateProperty], SubState: values[subStateProperty], }, nil } // validateUnitName 校验单元名称必须非空且不含首尾空白。 func validateUnitName(unitName string) error { if unitName == "" || strings.TrimSpace(unitName) != unitName { return errors.New("exact systemd unit name is required") } return nil } // commandError 包装命令执行错误,并附带去除首尾空白的命令输出作为诊断细节。 func commandError(action string, output []byte, err error) error { detail := strings.TrimSpace(string(output)) if detail == "" { return fmt.Errorf("%s: %w", action, err) } return fmt.Errorf("%s: %w: %s", action, err, detail) } var _ Manager = (*Systemctl)(nil)