diff --git a/daemon/sdnotify_linux_test.go b/daemon/sdnotify_linux_test.go new file mode 100644 index 00000000..c2ec15c9 --- /dev/null +++ b/daemon/sdnotify_linux_test.go @@ -0,0 +1,78 @@ +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package daemon + +import ( + "strconv" + "strings" + "testing" + "time" + + "golang.org/x/sys/unix" +) + +// TestSdNotifyMonotonicUsec checks that SdNotifyMonotonicUsec is probably not returning complete garbage. +func TestSdNotifyMonotonicUsec(t *testing.T) { + var resolution unix.Timespec + if err := unix.ClockGetres(unix.CLOCK_MONOTONIC, &resolution); err != nil { + if err == unix.EINVAL { + t.Log("CLOCK_MONOTONIC is not supported on this system") + if got := SdNotifyMonotonicUsec(); got != "" { + t.Errorf("SdNotifyMonotonicUsec() = %q; want empty string", got) + } + return + } + t.Fatalf("ClockGetres(CLOCK_MONOTONIC) failed: %v", err) + } + + now := func() uint64 { + got := SdNotifyMonotonicUsec() + t.Logf("SdNotifyMonotonicUsec() = %q", got) + if got == "" { + t.Fatal("SdNotifyMonotonicUsec() returned empty string on system which supports CLOCK_MONOTONIC") + } + fields := strings.SplitN(got, "=", 2) + if len(fields) != 2 { + t.Fatal("string is not a well-formed variable assignment") + } + tag, val := fields[0], fields[1] + if tag != "MONOTONIC_USEC" { + t.Errorf("expected tag MONOTONIC_USEC, got %q", tag) + } + if val[len(val)-1] != '\n' { + t.Errorf("expected value to end with newline, got %q", val) + } + ts, err := strconv.ParseUint(val[:len(val)-1], 10, 64) + if err != nil { + t.Fatalf("value %q is not well-formed: %v", val, err) + } + if ts == 0 { + // CLOCK_MONOTONIC is defined on Linux as the number of seconds + // since boot, per clock_gettime(2). A timestamp of zero is + // almost certainly bogus. + t.Fatal("timestamp is zero") + } + return ts + } + + start := now() + time.Sleep(time.Duration(resolution.Nano()) * 3) + ts := now() + if ts < start { + t.Errorf("timestamp went backwards: %d < %d", ts, start) + } else if ts == start { + t.Errorf("timestamp did not advance: %d == %d", ts, start) + } +} diff --git a/daemon/sdnotify_other.go b/daemon/sdnotify_other.go new file mode 100644 index 00000000..d6309495 --- /dev/null +++ b/daemon/sdnotify_other.go @@ -0,0 +1,22 @@ +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build !unix + +package daemon + +// SdNotifyMonotonicUsec returns the empty string on unsupported platforms. +func SdNotifyMonotonicUsec() string { + return "" +} diff --git a/daemon/sdnotify_unix.go b/daemon/sdnotify_unix.go new file mode 100644 index 00000000..bdbe6b0c --- /dev/null +++ b/daemon/sdnotify_unix.go @@ -0,0 +1,36 @@ +// Copyright 2025 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build unix + +package daemon + +import ( + "strconv" + + "golang.org/x/sys/unix" +) + +// SdNotifyMonotonicUsec returns a MONOTONIC_USEC=... assignment for the current time +// with a trailing newline included. This is typically used with [SdNotifyReloading]. +// +// If the monotonic clock is not available on the system, the empty string is returned. +func SdNotifyMonotonicUsec() string { + var ts unix.Timespec + if err := unix.ClockGettime(unix.CLOCK_MONOTONIC, &ts); err != nil { + // Monotonic clock is not available on this system. + return "" + } + return "MONOTONIC_USEC=" + strconv.FormatInt(ts.Nano()/1000, 10) + "\n" +} diff --git a/go.mod b/go.mod index b8f26d46..23912323 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,7 @@ module github.com/coreos/go-systemd/v22 go 1.23 -require github.com/godbus/dbus/v5 v5.1.0 +require ( + github.com/godbus/dbus/v5 v5.1.0 + golang.org/x/sys v0.1.0 +) diff --git a/go.sum b/go.sum index 024b2693..51d4034e 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,4 @@ github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=