mirror of
https://codeberg.org/cunicu/cunicu.git
synced 2025-09-26 21:01:14 +08:00
feat(systemd): Add support for watchdog timer
Signed-off-by: Steffen Vogel <post@steffenvogel.de>
This commit is contained in:
@@ -15,6 +15,7 @@ import (
|
||||
"cunicu.li/cunicu/pkg/device"
|
||||
"cunicu.li/cunicu/pkg/log"
|
||||
osx "cunicu.li/cunicu/pkg/os"
|
||||
"cunicu.li/cunicu/pkg/os/systemd"
|
||||
"cunicu.li/cunicu/pkg/signaling"
|
||||
"cunicu.li/cunicu/pkg/wg"
|
||||
)
|
||||
@@ -109,6 +110,11 @@ func (d *Daemon) Start() error {
|
||||
|
||||
signals := osx.SetupSignals(osx.SigUpdate)
|
||||
|
||||
wdt, err := d.watchdogTicker()
|
||||
if err != nil && !errors.Is(err, errNotSupported) {
|
||||
return fmt.Errorf("failed to get watchdog interval: %w", err)
|
||||
}
|
||||
|
||||
if err := d.setState(StateReady); err != nil {
|
||||
return fmt.Errorf("failed transition state: %w", err)
|
||||
}
|
||||
@@ -127,6 +133,12 @@ out:
|
||||
break out
|
||||
}
|
||||
|
||||
case <-wdt:
|
||||
if err := d.notify(systemd.NotifyWatchdog); err != nil {
|
||||
return fmt.Errorf("failed to notify systemd watchdog: %w", err)
|
||||
}
|
||||
d.logger.DebugV(20, "Watchdog tick")
|
||||
|
||||
case <-d.stop:
|
||||
break out
|
||||
}
|
||||
@@ -252,6 +264,18 @@ func (d *Daemon) CreateDevices() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Daemon) watchdogTicker() (<-chan time.Time, error) {
|
||||
wdInterval, err := systemd.WatchdogEnabled(true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if wdInterval == 0 {
|
||||
d.logger.DebugV(5, "Not started via systemd. Disabling watchdog")
|
||||
return nil, errNotSupported
|
||||
}
|
||||
|
||||
return time.NewTicker(wdInterval / 2).C, nil
|
||||
}
|
||||
|
||||
func (d *Daemon) setState(s State) error {
|
||||
d.state = s
|
||||
|
||||
|
69
pkg/os/systemd/watchdog_linux.go
Normal file
69
pkg/os/systemd/watchdog_linux.go
Normal file
@@ -0,0 +1,69 @@
|
||||
// SPDX-FileCopyrightText: 2015 CoreOS, Inc.
|
||||
// SPDX-FileCopyrightText: 2023-2024 Steffen Vogel <post@steffenvogel.de>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//go:build linux
|
||||
|
||||
package systemd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
var ErrNegativeWatchdogInterval = errors.New("WATCHDOG_USEC must be a positive number")
|
||||
|
||||
// WatchdogEnabled returns watchdog information for a service.
|
||||
// Processes should call daemon.SdNotify(false, daemon.SdNotifyWatchdog) every
|
||||
// time / 2.
|
||||
// If `unsetEnv` is true, the environment variables `WATCHDOG_USEC` and
|
||||
// `WATCHDOG_PID` will be unconditionally unset.
|
||||
//
|
||||
// It returns one of the following:
|
||||
// (0, nil) - watchdog isn't enabled or we aren't the watched PID.
|
||||
// (0, err) - an error happened (e.g. error converting time).
|
||||
// (time, nil) - watchdog is enabled and we can send ping. time is delay
|
||||
// before inactive service will be killed.
|
||||
func WatchdogEnabled(unsetEnv bool) (interval time.Duration, err error) {
|
||||
wUSec := os.Getenv("WATCHDOG_USEC")
|
||||
wPID := os.Getenv("WATCHDOG_PID")
|
||||
|
||||
if unsetEnv {
|
||||
wUSecErr := os.Unsetenv("WATCHDOG_USEC")
|
||||
wPIDErr := os.Unsetenv("WATCHDOG_PID")
|
||||
if wUSecErr != nil {
|
||||
return 0, wUSecErr
|
||||
}
|
||||
if wPIDErr != nil {
|
||||
return 0, wPIDErr
|
||||
}
|
||||
}
|
||||
|
||||
if wUSec == "" {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
s, err := strconv.Atoi(wUSec)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to convert WATCHDOG_USEC: %w", err)
|
||||
} else if s <= 0 {
|
||||
return 0, ErrNegativeWatchdogInterval
|
||||
}
|
||||
|
||||
interval = time.Duration(s) * time.Microsecond
|
||||
|
||||
if wPID == "" {
|
||||
return interval, nil
|
||||
}
|
||||
|
||||
if p, err := strconv.Atoi(wPID); err != nil {
|
||||
return 0, fmt.Errorf("failed to convert WATCHDOG_PID: %w", err)
|
||||
} else if os.Getpid() != p {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
return interval, nil
|
||||
}
|
12
pkg/os/systemd/watchdog_others.go
Normal file
12
pkg/os/systemd/watchdog_others.go
Normal file
@@ -0,0 +1,12 @@
|
||||
// SPDX-FileCopyrightText: 2023-2024 Steffen Vogel <post@steffenvogel.de>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//go:build !linux
|
||||
|
||||
package systemd
|
||||
|
||||
import "time"
|
||||
|
||||
func WatchdogEnabled(_ bool) (interval time.Duration, err error) {
|
||||
return 0, nil
|
||||
}
|
72
pkg/os/systemd/watchdog_test.go
Normal file
72
pkg/os/systemd/watchdog_test.go
Normal file
@@ -0,0 +1,72 @@
|
||||
// SPDX-FileCopyrightText: 2016 CoreOS, Inc.
|
||||
// SPDX-FileCopyrightText: 2023-2024 Steffen Vogel <post@steffenvogel.de>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//go:build linux
|
||||
|
||||
package systemd_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"cunicu.li/cunicu/pkg/os/systemd"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Context("Watchdog", func() {
|
||||
myPID := strconv.Itoa(os.Getpid())
|
||||
|
||||
DescribeTable("enabled", func(usec string, pid string, unsetEnv bool, expectedErr error, expectedDelay time.Duration) {
|
||||
if usec != "" {
|
||||
err := os.Setenv("WATCHDOG_USEC", usec)
|
||||
Expect(err).To(Succeed())
|
||||
} else {
|
||||
err := os.Unsetenv("WATCHDOG_USEC")
|
||||
Expect(err).To(Succeed())
|
||||
}
|
||||
|
||||
if pid != "" {
|
||||
err := os.Setenv("WATCHDOG_PID", pid)
|
||||
Expect(err).To(Succeed())
|
||||
} else {
|
||||
err := os.Unsetenv("WATCHDOG_PID")
|
||||
Expect(err).To(Succeed())
|
||||
}
|
||||
|
||||
delay, err := systemd.WatchdogEnabled(unsetEnv)
|
||||
Expect(delay).To(Equal(expectedDelay))
|
||||
|
||||
if expectedErr != nil {
|
||||
Expect(err).To(MatchError(expectedErr))
|
||||
} else {
|
||||
Expect(err).To(Succeed())
|
||||
}
|
||||
|
||||
if unsetEnv {
|
||||
Expect(os.Getenv("WATCHDOG_PID")).To(BeEmpty())
|
||||
Expect(os.Getenv("WATCHDOG_USEC")).To(BeEmpty())
|
||||
}
|
||||
},
|
||||
// Success cases
|
||||
Entry(nil, "100", myPID, true, nil, 100*time.Microsecond),
|
||||
Entry(nil, "50", myPID, true, nil, 50*time.Microsecond),
|
||||
Entry(nil, "1", myPID, false, nil, 1*time.Microsecond),
|
||||
Entry(nil, "1", "", true, nil, 1*time.Microsecond),
|
||||
|
||||
// No-op cases
|
||||
Entry("WATCHDOG_USEC not set", "", myPID, true, nil, time.Duration(0)),
|
||||
Entry("WATCHDOG_PID doesn't match", "1", "0", false, nil, time.Duration(0)),
|
||||
Entry("Both not set", "", "", true, nil, time.Duration(0)),
|
||||
|
||||
// Failure cases
|
||||
Entry("Negative USEC", "-1", myPID, true, systemd.ErrNegativeWatchdogInterval, time.Duration(0)),
|
||||
Entry("Non-integer USEC value", "string", "1", false, strconv.ErrSyntax, time.Duration(0)),
|
||||
Entry("Non-integer PID value", "1", "string", true, strconv.ErrSyntax, time.Duration(0)),
|
||||
Entry("Everything wrong", "stringa", "stringb", false, strconv.ErrSyntax, time.Duration(0)),
|
||||
Entry("Everything wrong", "-10239", "-eleventythree", true, systemd.ErrNegativeWatchdogInterval, time.Duration(0)),
|
||||
)
|
||||
})
|
Reference in New Issue
Block a user