core: implement TimerContext/Runtime for io.systemd.Unit.List + tests

Add varlink context and runtime builders for .timer units:

TimerContext: MonotonicTimers (with MonotonicTimerBase enum), CalendarTimers, Unit, OnClockChange, OnTimezoneChange, AccuracyUSec, RandomizedDelayUSec, RandomizedOffsetUSec, FixedRandomDelay, Persistent, WakeSystem, RemainAfterElapse, DeferReactivation
TimerRuntime: Result (TimerResult enum), NextElapseUSecRealtime, NextElapseUSecMonotonic, LastTriggerUSec

MonotonicTimerSpec and CalendarTimerSpec are separate types since they have different value types (int vs string). MonotonicTimerBase and TimerResult are proper varlink enum types.

Compared to the old io-systemd-Unit-List branch, this adds RandomizedOffsetUSec and DeferReactivation (both present in D-Bus but previously missing), and adds full runtime fields.

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Ivan Kruglov
2026-05-11 04:32:14 -07:00
parent ed4740a0dc
commit ae619b1224
8 changed files with 182 additions and 2 deletions
+1
View File
@@ -76,6 +76,7 @@ libcore_sources = files(
'varlink-path.c',
'varlink-scope.c',
'varlink-swap.c',
'varlink-timer.c',
'varlink-unit.c',
)
+77
View File
@@ -0,0 +1,77 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "sd-json.h"
#include "calendarspec.h"
#include "json-util.h"
#include "timer.h"
#include "varlink-timer.h"
static int timers_build_json(sd_json_variant **ret, const char *name, void *userdata) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
TimerValue *values = userdata;
int r;
assert(ret);
LIST_FOREACH(value, value, values) {
_cleanup_free_ char *base_name = NULL;
base_name = timer_base_to_usec_string(value->base);
if (!base_name)
return -ENOMEM;
if (value->base == TIMER_CALENDAR) {
_cleanup_free_ char *calendar = NULL;
r = calendar_spec_to_string(value->calendar_spec, &calendar);
if (r < 0)
return log_debug_errno(r, "Failed to convert calendar spec into string: %m");
r = sd_json_variant_append_arraybo(
&v,
JSON_BUILD_PAIR_ENUM("base", base_name),
SD_JSON_BUILD_PAIR_STRING("calendar", calendar));
} else
r = sd_json_variant_append_arraybo(
&v,
JSON_BUILD_PAIR_ENUM("base", base_name),
SD_JSON_BUILD_PAIR_UNSIGNED("usec", value->value));
if (r < 0)
return r;
}
*ret = TAKE_PTR(v);
return 0;
}
int timer_context_build_json(sd_json_variant **ret, const char *name, void *userdata) {
Timer *t = ASSERT_PTR(TIMER(userdata));
Unit *trigger = UNIT_TRIGGER(UNIT(t));
return sd_json_buildo(
ASSERT_PTR(ret),
JSON_BUILD_PAIR_CALLBACK_NON_NULL("Timers", timers_build_json, t->values),
JSON_BUILD_PAIR_STRING_NON_EMPTY("Unit", trigger ? trigger->id : NULL),
SD_JSON_BUILD_PAIR_BOOLEAN("OnClockChange", t->on_clock_change),
SD_JSON_BUILD_PAIR_BOOLEAN("OnTimezoneChange", t->on_timezone_change),
JSON_BUILD_PAIR_FINITE_USEC("AccuracyUSec", t->accuracy_usec),
JSON_BUILD_PAIR_FINITE_USEC("RandomizedDelayUSec", t->random_delay_usec),
JSON_BUILD_PAIR_FINITE_USEC("RandomizedOffsetUSec", t->random_offset_usec),
SD_JSON_BUILD_PAIR_BOOLEAN("FixedRandomDelay", t->fixed_random_delay),
SD_JSON_BUILD_PAIR_BOOLEAN("Persistent", t->persistent),
SD_JSON_BUILD_PAIR_BOOLEAN("WakeSystem", t->wake_system),
SD_JSON_BUILD_PAIR_BOOLEAN("RemainAfterElapse", t->remain_after_elapse),
SD_JSON_BUILD_PAIR_BOOLEAN("DeferReactivation", t->defer_reactivation));
}
int timer_runtime_build_json(sd_json_variant **ret, const char *name, void *userdata) {
Timer *t = ASSERT_PTR(TIMER(userdata));
return sd_json_buildo(
ASSERT_PTR(ret),
JSON_BUILD_PAIR_ENUM("Result", timer_result_to_string(t->result)),
JSON_BUILD_PAIR_FINITE_USEC("NextElapseUSecRealtime", t->next_elapse_realtime),
JSON_BUILD_PAIR_FINITE_USEC("NextElapseUSecMonotonic", timer_next_elapse_monotonic(t)),
JSON_BUILD_PAIR_DUAL_TIMESTAMP_NON_NULL("LastTriggerUSec", &t->last_trigger));
}
+7
View File
@@ -0,0 +1,7 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "core-forward.h"
int timer_context_build_json(sd_json_variant **ret, const char *name, void *userdata);
int timer_runtime_build_json(sd_json_variant **ret, const char *name, void *userdata);
+3
View File
@@ -32,6 +32,7 @@
#include "varlink-path.h"
#include "varlink-scope.h"
#include "varlink-swap.h"
#include "varlink-timer.h"
#include "varlink-unit.h"
#include "varlink-util.h"
@@ -169,6 +170,7 @@ static int unit_context_build_json(sd_json_variant **ret, const char *name, void
[UNIT_SCOPE] = scope_context_build_json,
[UNIT_SERVICE] = service_context_build_json,
[UNIT_SWAP] = swap_context_build_json,
[UNIT_TIMER] = timer_context_build_json,
};
return sd_json_buildo(
@@ -337,6 +339,7 @@ static int unit_runtime_build_json(sd_json_variant **ret, const char *name, void
[UNIT_PATH] = path_runtime_build_json,
[UNIT_SCOPE] = scope_runtime_build_json,
[UNIT_SWAP] = swap_runtime_build_json,
[UNIT_TIMER] = timer_runtime_build_json,
};
return sd_json_buildo(
+75 -2
View File
@@ -1081,6 +1081,70 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE(
SD_VARLINK_FIELD_COMMENT("Reference GID"),
SD_VARLINK_DEFINE_FIELD(GID, SD_VARLINK_INT, SD_VARLINK_NULLABLE));
/* TimerContext
* https://www.freedesktop.org/software/systemd/man/latest/systemd.timer.html */
SD_VARLINK_DEFINE_ENUM_TYPE(
TimerBase,
SD_VARLINK_DEFINE_ENUM_VALUE(OnActiveUSec),
SD_VARLINK_DEFINE_ENUM_VALUE(OnBootUSec),
SD_VARLINK_DEFINE_ENUM_VALUE(OnStartupUSec),
SD_VARLINK_DEFINE_ENUM_VALUE(OnUnitActiveUSec),
SD_VARLINK_DEFINE_ENUM_VALUE(OnUnitInactiveUSec),
SD_VARLINK_DEFINE_ENUM_VALUE(OnCalendar));
static SD_VARLINK_DEFINE_STRUCT_TYPE(
TimerSpec,
SD_VARLINK_FIELD_COMMENT("Timer base type"),
SD_VARLINK_DEFINE_FIELD_BY_TYPE(base, TimerBase, 0),
SD_VARLINK_FIELD_COMMENT("Timer value in microseconds (for monotonic timers)"),
SD_VARLINK_DEFINE_FIELD(usec, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Calendar specification string (for calendar timers)"),
SD_VARLINK_DEFINE_FIELD(calendar, SD_VARLINK_STRING, SD_VARLINK_NULLABLE));
static SD_VARLINK_DEFINE_STRUCT_TYPE(
TimerContext,
SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.timer.html#OnActiveSec="),
SD_VARLINK_DEFINE_FIELD_BY_TYPE(Timers, TimerSpec, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.timer.html#Unit="),
SD_VARLINK_DEFINE_FIELD(Unit, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.timer.html#OnClockChange="),
SD_VARLINK_DEFINE_FIELD(OnClockChange, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.timer.html#OnClockChange="),
SD_VARLINK_DEFINE_FIELD(OnTimezoneChange, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.timer.html#AccuracySec="),
SD_VARLINK_DEFINE_FIELD(AccuracyUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.timer.html#RandomizedDelaySec="),
SD_VARLINK_DEFINE_FIELD(RandomizedDelayUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.timer.html#RandomizedOffsetSec="),
SD_VARLINK_DEFINE_FIELD(RandomizedOffsetUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.timer.html#FixedRandomDelay="),
SD_VARLINK_DEFINE_FIELD(FixedRandomDelay, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.timer.html#Persistent="),
SD_VARLINK_DEFINE_FIELD(Persistent, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.timer.html#WakeSystem="),
SD_VARLINK_DEFINE_FIELD(WakeSystem, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.timer.html#RemainAfterElapse="),
SD_VARLINK_DEFINE_FIELD(RemainAfterElapse, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.timer.html#DeferReactivation="),
SD_VARLINK_DEFINE_FIELD(DeferReactivation, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE));
SD_VARLINK_DEFINE_ENUM_TYPE(
TimerResult,
SD_VARLINK_DEFINE_ENUM_VALUE(success),
SD_VARLINK_DEFINE_ENUM_VALUE(resources),
SD_VARLINK_DEFINE_ENUM_VALUE(start_limit_hit));
static SD_VARLINK_DEFINE_STRUCT_TYPE(
TimerRuntime,
SD_VARLINK_FIELD_COMMENT("Result of timer operation"),
SD_VARLINK_DEFINE_FIELD_BY_TYPE(Result, TimerResult, 0),
SD_VARLINK_FIELD_COMMENT("Next elapse time in realtime clock"),
SD_VARLINK_DEFINE_FIELD(NextElapseUSecRealtime, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Next elapse time in monotonic clock"),
SD_VARLINK_DEFINE_FIELD(NextElapseUSecMonotonic, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Last time the timer triggered"),
SD_VARLINK_DEFINE_FIELD_BY_TYPE(LastTriggerUSec, Timestamp, SD_VARLINK_NULLABLE));
/* Service-specific types */
/* Keep in sync with service_type_table[] in src/core/service.c */
@@ -1279,7 +1343,9 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE(
SD_VARLINK_FIELD_COMMENT("The scope context of the unit"),
SD_VARLINK_DEFINE_FIELD_BY_TYPE(Scope, ScopeContext, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The swap context of the unit"),
SD_VARLINK_DEFINE_FIELD_BY_TYPE(Swap, SwapContext, SD_VARLINK_NULLABLE));
SD_VARLINK_DEFINE_FIELD_BY_TYPE(Swap, SwapContext, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The timer context of the unit"),
SD_VARLINK_DEFINE_FIELD_BY_TYPE(Timer, TimerContext, SD_VARLINK_NULLABLE));
static SD_VARLINK_DEFINE_STRUCT_TYPE(
ActivationDetails,
@@ -1459,7 +1525,9 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE(
SD_VARLINK_FIELD_COMMENT("The scope runtime of the unit"),
SD_VARLINK_DEFINE_FIELD_BY_TYPE(Scope, ScopeRuntime, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The swap runtime of the unit"),
SD_VARLINK_DEFINE_FIELD_BY_TYPE(Swap, SwapRuntime, SD_VARLINK_NULLABLE));
SD_VARLINK_DEFINE_FIELD_BY_TYPE(Swap, SwapRuntime, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The timer runtime of the unit"),
SD_VARLINK_DEFINE_FIELD_BY_TYPE(Timer, TimerRuntime, SD_VARLINK_NULLABLE));
static SD_VARLINK_DEFINE_METHOD_FULL(
List,
@@ -1633,6 +1701,11 @@ SD_VARLINK_DEFINE_INTERFACE(
&vl_type_SwapContext,
&vl_type_SwapResult,
&vl_type_SwapRuntime,
&vl_type_TimerBase,
&vl_type_TimerSpec,
&vl_type_TimerContext,
&vl_type_TimerResult,
&vl_type_TimerRuntime,
/* UnitContext enums */
&vl_type_CollectMode,
+2
View File
@@ -34,6 +34,8 @@ extern const sd_varlink_symbol vl_type_PathType;
extern const sd_varlink_symbol vl_type_PathResult;
extern const sd_varlink_symbol vl_type_ScopeResult;
extern const sd_varlink_symbol vl_type_SwapResult;
extern const sd_varlink_symbol vl_type_TimerBase;
extern const sd_varlink_symbol vl_type_TimerResult;
extern const sd_varlink_symbol vl_type_CollectMode;
extern const sd_varlink_symbol vl_type_JobMode;
extern const sd_varlink_symbol vl_type_ServiceType;
+11
View File
@@ -13,6 +13,7 @@
#include "swap.h"
#include "tests.h"
#include "test-varlink-idl-util.h"
#include "timer.h"
#include "unit.h"
#include "varlink-idl-common.h"
#include "varlink-io.systemd.Unit.h"
@@ -78,6 +79,16 @@ TEST(unit_enums_idl) {
/* SwapRuntime enums */
TEST_IDL_ENUM(SwapResult, swap_result, vl_type_SwapResult);
/* TimerContext enums */
for (TimerBase b = 0; b < _TIMER_BASE_MAX; b++) {
_cleanup_free_ char *s = timer_base_to_usec_string(b);
assert_se(s);
test_enum_to_string_name(s, &vl_type_TimerBase);
}
/* TimerRuntime enums */
TEST_IDL_ENUM(TimerResult, timer_result, vl_type_TimerResult);
/* UnitContext enums */
TEST_IDL_ENUM(CollectMode, collect_mode, vl_type_CollectMode);
TEST_IDL_ENUM(EmergencyAction, emergency_action, vl_type_EmergencyAction);
@@ -265,6 +265,12 @@ if test -n "$swap_id"; then
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$swap_params" | jq -e '.context.Swap'
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$swap_params" | jq -e '.runtime.Swap'
fi
# test for TimerContext/Runtime
timer_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "timer" and .runtime.LoadState == "loaded") .context.ID' | grep -v null | tail -n 1)
test -n "$timer_id"
timer_params=$(jq -cn --arg name "$timer_id" '{name: $name}')
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$timer_params" | jq -e '.context.Timer'
varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$timer_params" | jq -e '.runtime.Timer'
# test io.systemd.Metrics
varlinkctl info /run/systemd/report/io.systemd.Manager