]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/timedate/timedated.c
license: LGPL-2.1+ -> LGPL-2.1-or-later
[thirdparty/systemd.git] / src / timedate / timedated.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
f401e48c 2
f401e48c 3#include <errno.h>
ca78ad1d
ZJS
4#include <sys/stat.h>
5#include <sys/types.h>
f401e48c
LP
6#include <unistd.h>
7
40ca29a1 8#include "sd-bus.h"
f4f15635
LP
9#include "sd-event.h"
10#include "sd-messages.h"
40ca29a1 11
b5efdb8a 12#include "alloc-util.h"
96aad8d1 13#include "bus-common-errors.h"
f4f15635 14#include "bus-error.h"
40af3d02 15#include "bus-get-properties.h"
9b71e4ab 16#include "bus-locator.h"
ac9f55ed 17#include "bus-log-control-api.h"
807542be 18#include "bus-map-properties.h"
269e4d2d 19#include "bus-polkit.h"
f4f15635 20#include "clock-util.h"
afaae43b 21#include "conf-files.h"
f4f15635 22#include "def.h"
afaae43b 23#include "fd-util.h"
f4f15635 24#include "fileio-label.h"
e4de7287 25#include "fileio.h"
f4f15635 26#include "fs-util.h"
5d280742
YW
27#include "hashmap.h"
28#include "list.h"
1f47bc33 29#include "main-func.h"
0a970718 30#include "memory-util.h"
36dd5ffd 31#include "missing_capability.h"
f4f15635 32#include "path-util.h"
d7b8eec7 33#include "selinux-util.h"
fc021a5b 34#include "service-util.h"
754f0269 35#include "signal-util.h"
5d280742 36#include "string-util.h"
f4f15635 37#include "strv.h"
5d280742
YW
38#include "unit-def.h"
39#include "unit-name.h"
ee104e11 40#include "user-util.h"
f401e48c
LP
41
42#define NULL_ADJTIME_UTC "0.0 0 0\n0\nUTC\n"
43#define NULL_ADJTIME_LOCAL "0.0 0 0\n0\nLOCAL\n"
44
afaae43b
ZJS
45#define UNIT_LIST_DIRS (const char* const*) CONF_PATHS_STRV("systemd/ntp-units.d")
46
5d280742
YW
47typedef struct UnitStatusInfo {
48 char *name;
49 char *load_state;
50 char *unit_file_state;
51 char *active_state;
cf3872bd 52 char *path;
5d280742
YW
53
54 LIST_FIELDS(struct UnitStatusInfo, units);
55} UnitStatusInfo;
56
40ca29a1 57typedef struct Context {
d200735e
MS
58 char *zone;
59 bool local_rtc;
40ca29a1 60 Hashmap *polkit_registry;
2770af85 61 sd_bus_message *cache;
5d280742 62
3af0a96c 63 sd_bus_slot *slot_job_removed;
3af0a96c 64
5d280742 65 LIST_HEAD(UnitStatusInfo, units);
40ca29a1 66} Context;
f401e48c 67
ad7fb943
ZJS
68#define log_unit_full(unit, level, error, ...) \
69 ({ \
70 const UnitStatusInfo *_u = (unit); \
71 log_object_internal(level, error, PROJECT_FILE, __LINE__, __func__, \
72 "UNIT=", _u->name, NULL, NULL, ##__VA_ARGS__); \
73 })
74
75#define log_unit_debug(unit, ...) log_unit_full(unit, LOG_DEBUG, 0, ##__VA_ARGS__)
76#define log_unit_info(unit, ...) log_unit_full(unit, LOG_INFO, 0, ##__VA_ARGS__)
77#define log_unit_notice(unit, ...) log_unit_full(unit, LOG_NOTICE, 0, ##__VA_ARGS__)
78#define log_unit_warning(unit, ...) log_unit_full(unit, LOG_WARNING, 0, ##__VA_ARGS__)
79#define log_unit_error(unit, ...) log_unit_full(unit, LOG_ERR, 0, ##__VA_ARGS__)
80
81#define log_unit_debug_errno(unit, error, ...) log_unit_full(unit, LOG_DEBUG, error, ##__VA_ARGS__)
82#define log_unit_info_errno(unit, error, ...) log_unit_full(unit, LOG_INFO, error, ##__VA_ARGS__)
83#define log_unit_notice_errno(unit, error, ...) log_unit_full(unit, LOG_NOTICE, error, ##__VA_ARGS__)
84#define log_unit_warning_errno(unit, error, ...) log_unit_full(unit, LOG_WARNING, error, ##__VA_ARGS__)
85#define log_unit_error_errno(unit, error, ...) log_unit_full(unit, LOG_ERR, error, ##__VA_ARGS__)
86
5d280742
YW
87static void unit_status_info_clear(UnitStatusInfo *p) {
88 assert(p);
89
90 p->load_state = mfree(p->load_state);
91 p->unit_file_state = mfree(p->unit_file_state);
92 p->active_state = mfree(p->active_state);
93}
94
95static void unit_status_info_free(UnitStatusInfo *p) {
96 assert(p);
97
98 unit_status_info_clear(p);
99 free(p->name);
cf3872bd 100 free(p->path);
5d280742
YW
101 free(p);
102}
103
1f47bc33 104static void context_clear(Context *c) {
5d280742
YW
105 UnitStatusInfo *p;
106
40ca29a1 107 assert(c);
f401e48c 108
82d115d9 109 free(c->zone);
36e34057 110 bus_verify_polkit_async_registry_free(c->polkit_registry);
2770af85 111 sd_bus_message_unref(c->cache);
5d280742 112
3af0a96c 113 sd_bus_slot_unref(c->slot_job_removed);
3af0a96c 114
5d280742
YW
115 while ((p = c->units)) {
116 LIST_REMOVE(units, c->units, p);
117 unit_status_info_free(p);
118 }
119}
120
ad7fb943 121static int context_add_ntp_service(Context *c, const char *s, const char *source) {
5d280742
YW
122 UnitStatusInfo *u;
123
124 if (!unit_name_is_valid(s, UNIT_NAME_PLAIN))
125 return -EINVAL;
126
127 /* Do not add this if it is already listed */
128 LIST_FOREACH(units, u, c->units)
129 if (streq(u->name, s))
130 return 0;
131
132 u = new0(UnitStatusInfo, 1);
133 if (!u)
134 return -ENOMEM;
135
136 u->name = strdup(s);
137 if (!u->name) {
138 free(u);
139 return -ENOMEM;
140 }
141
142 LIST_APPEND(units, c->units, u);
ad7fb943 143 log_unit_debug(u, "added from %s.", source);
5d280742
YW
144
145 return 0;
146}
147
afaae43b 148static int context_parse_ntp_services_from_environment(Context *c) {
5d280742
YW
149 const char *env, *p;
150 int r;
151
152 assert(c);
153
154 env = getenv("SYSTEMD_TIMEDATED_NTP_SERVICES");
afaae43b 155 if (!env)
5d280742 156 return 0;
afaae43b 157
ad7fb943 158 log_debug("Using list of ntp services from environment variable $SYSTEMD_TIMEDATED_NTP_SERVICES=%s.", env);
5d280742
YW
159
160 for (p = env;;) {
161 _cleanup_free_ char *word = NULL;
162
163 r = extract_first_word(&p, &word, ":", 0);
164 if (r == 0)
165 break;
166 if (r == -ENOMEM)
167 return log_oom();
168 if (r < 0) {
169 log_error("Invalid syntax, ignoring: %s", env);
170 break;
171 }
172
ad7fb943 173 r = context_add_ntp_service(c, word, "$SYSTEMD_TIMEDATED_NTP_SERVICES");
5d280742
YW
174 if (r < 0)
175 log_warning_errno(r, "Failed to add NTP service \"%s\", ignoring: %m", word);
176 }
177
afaae43b
ZJS
178 return 1;
179}
180
181static int context_parse_ntp_services_from_disk(Context *c) {
182 _cleanup_strv_free_ char **files = NULL;
183 char **f;
184 int r;
185
186 r = conf_files_list_strv(&files, ".list", NULL, CONF_FILES_FILTER_MASKED, UNIT_LIST_DIRS);
187 if (r < 0)
188 return log_error_errno(r, "Failed to enumerate .list files: %m");
189
190 STRV_FOREACH(f, files) {
191 _cleanup_fclose_ FILE *file = NULL;
192
193 log_debug("Reading file '%s'", *f);
194
195 r = fopen_unlocked(*f, "re", &file);
196 if (r < 0) {
197 log_error_errno(r, "Failed to open %s, ignoring: %m", *f);
198 continue;
199 }
200
201 for (;;) {
202 _cleanup_free_ char *line = NULL;
203 const char *word;
204
205 r = read_line(file, LINE_MAX, &line);
206 if (r < 0) {
207 log_error_errno(r, "Failed to read %s, ignoring: %m", *f);
208 continue;
209 }
210 if (r == 0)
211 break;
212
213 word = strstrip(line);
214 if (isempty(word) || startswith("#", word))
215 continue;
216
ad7fb943 217 r = context_add_ntp_service(c, word, *f);
afaae43b
ZJS
218 if (r < 0)
219 log_warning_errno(r, "Failed to add NTP service \"%s\", ignoring: %m", word);
220 }
221 }
222
223 return 1;
224}
225
226static int context_parse_ntp_services(Context *c) {
227 int r;
228
229 r = context_parse_ntp_services_from_environment(c);
230 if (r != 0)
231 return r;
232
233 return context_parse_ntp_services_from_disk(c);
5d280742
YW
234}
235
236static int context_ntp_service_is_active(Context *c) {
237 UnitStatusInfo *info;
238 int count = 0;
239
240 assert(c);
241
242 /* Call context_update_ntp_status() to update UnitStatusInfo before calling this. */
243
244 LIST_FOREACH(units, info, c->units)
84a87726 245 count += !STRPTR_IN_SET(info->active_state, "inactive", "failed");
5d280742
YW
246
247 return count;
248}
249
5d280742
YW
250static int context_ntp_service_exists(Context *c) {
251 UnitStatusInfo *info;
252 int count = 0;
253
254 assert(c);
255
256 /* Call context_update_ntp_status() to update UnitStatusInfo before calling this. */
257
258 LIST_FOREACH(units, info, c->units)
259 count += streq_ptr(info->load_state, "loaded");
260
261 return count;
f401e48c
LP
262}
263
40ca29a1 264static int context_read_data(Context *c) {
424a19f8 265 _cleanup_free_ char *t = NULL;
40ca29a1
LP
266 int r;
267
268 assert(c);
f401e48c 269
5c904ba5
LP
270 r = get_timezone(&t);
271 if (r == -EINVAL)
272 log_warning_errno(r, "/etc/localtime should be a symbolic link to a time zone data file in /usr/share/zoneinfo/.");
273 else if (r < 0)
274 log_warning_errno(r, "Failed to get target of /etc/localtime: %m");
92c4ef2d 275
f9ecfd3b 276 free_and_replace(c->zone, t);
f401e48c 277
6369641d 278 c->local_rtc = clock_is_localtime(NULL) > 0;
f401e48c
LP
279
280 return 0;
281}
282
40ca29a1 283static int context_write_data_timezone(Context *c) {
424a19f8 284 _cleanup_free_ char *p = NULL;
9193af0f 285 const char *source;
40ca29a1
LP
286
287 assert(c);
e19a21a8 288
9193af0f
LP
289 /* No timezone is very similar to UTC. Hence in either of these cases link the UTC file in. Except if
290 * it isn't installed, in which case we remove the symlink altogether. Since glibc defaults to an
291 * internal version of UTC in that case behaviour is mostly equivalent. We still prefer creating the
292 * symlink though, since things are more self explanatory then. */
293
294 if (isempty(c->zone) || streq(c->zone, "UTC")) {
295
296 if (access("/usr/share/zoneinfo/UTC", F_OK) < 0) {
297
298 if (unlink("/etc/localtime") < 0 && errno != ENOENT)
299 return -errno;
f401e48c 300
9193af0f
LP
301 return 0;
302 }
303
304 source = "../usr/share/zoneinfo/UTC";
305 } else {
306 p = path_join("../usr/share/zoneinfo", c->zone);
307 if (!p)
308 return -ENOMEM;
309
310 source = p;
311 }
f401e48c 312
9193af0f 313 return symlink_atomic(source, "/etc/localtime");
f401e48c
LP
314}
315
40ca29a1 316static int context_write_data_local_rtc(Context *c) {
7fd1b19b 317 _cleanup_free_ char *s = NULL, *w = NULL;
d9cb4bba 318 int r;
f401e48c 319
40ca29a1
LP
320 assert(c);
321
f401e48c
LP
322 r = read_full_file("/etc/adjtime", &s, NULL);
323 if (r < 0) {
324 if (r != -ENOENT)
325 return r;
326
40ca29a1 327 if (!c->local_rtc)
f401e48c
LP
328 return 0;
329
330 w = strdup(NULL_ADJTIME_LOCAL);
331 if (!w)
332 return -ENOMEM;
333 } else {
c9410dd4 334 char *p;
8f462d87 335 const char *e = "\n"; /* default if there is less than 3 lines */
c9410dd4 336 const char *prepend = "";
f401e48c
LP
337 size_t a, b;
338
c9410dd4 339 p = strchrnul(s, '\n');
cb971cc0 340 if (*p == '\0')
c9410dd4
MP
341 /* only one line, no \n terminator */
342 prepend = "\n0\n";
cb971cc0 343 else if (p[1] == '\0') {
c9410dd4
MP
344 /* only one line, with \n terminator */
345 ++p;
346 prepend = "0\n";
347 } else {
348 p = strchr(p+1, '\n');
349 if (!p) {
350 /* only two lines, no \n terminator */
351 prepend = "\n";
352 p = s + strlen(s);
353 } else {
354 char *end;
355 /* third line might have a \n terminator or not */
356 p++;
357 end = strchr(p, '\n');
358 /* if we actually have a fourth line, use that as suffix "e", otherwise the default \n */
359 if (end)
360 e = end;
361 }
362 }
f401e48c
LP
363
364 a = p - s;
365 b = strlen(e);
366
c9410dd4 367 w = new(char, a + (c->local_rtc ? 5 : 3) + strlen(prepend) + b + 1);
d257f05a 368 if (!w)
f401e48c 369 return -ENOMEM;
f401e48c 370
c9410dd4 371 *(char*) mempcpy(stpcpy(stpcpy(mempcpy(w, s, a), prepend), c->local_rtc ? "LOCAL" : "UTC"), e, b) = 0;
f401e48c
LP
372
373 if (streq(w, NULL_ADJTIME_UTC)) {
d257f05a 374 if (unlink("/etc/adjtime") < 0)
f401e48c
LP
375 if (errno != ENOENT)
376 return -errno;
f401e48c
LP
377
378 return 0;
379 }
380 }
40ca29a1 381
a9ba0e32
CG
382 r = mac_selinux_init();
383 if (r < 0)
384 return r;
385
d257f05a 386 return write_string_file_atomic_label("/etc/adjtime", w);
f401e48c
LP
387}
388
5d280742
YW
389static int context_update_ntp_status(Context *c, sd_bus *bus, sd_bus_message *m) {
390 static const struct bus_properties_map map[] = {
391 { "LoadState", "s", NULL, offsetof(UnitStatusInfo, load_state) },
392 { "ActiveState", "s", NULL, offsetof(UnitStatusInfo, active_state) },
393 { "UnitFileState", "s", NULL, offsetof(UnitStatusInfo, unit_file_state) },
394 {}
395 };
5d280742 396 UnitStatusInfo *u;
c5f0532f
LP
397 int r;
398
40ca29a1 399 assert(c);
c5f0532f
LP
400 assert(bus);
401
2770af85
YW
402 /* Suppress calling context_update_ntp_status() multiple times within single DBus transaction. */
403 if (m) {
404 if (m == c->cache)
405 return 0;
2aa4c315 406
2770af85
YW
407 sd_bus_message_unref(c->cache);
408 c->cache = sd_bus_message_ref(m);
409 }
2aa4c315 410
5d280742
YW
411 LIST_FOREACH(units, u, c->units) {
412 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
5d280742 413 _cleanup_free_ char *path = NULL;
c5f0532f 414
5d280742 415 unit_status_info_clear(u);
40ca29a1 416
5d280742
YW
417 path = unit_dbus_path_from_name(u->name);
418 if (!path)
419 return -ENOMEM;
420
421 r = bus_map_all_properties(
422 bus,
423 "org.freedesktop.systemd1",
424 path,
425 map,
426 BUS_MAP_STRDUP,
427 &error,
428 NULL,
429 u);
430 if (r < 0)
ad7fb943 431 return log_unit_error_errno(u, r, "Failed to get properties: %s", bus_error_message(&error, r));
5d280742 432 }
c5f0532f 433
40ca29a1 434 return 0;
c5f0532f
LP
435}
436
3af0a96c 437static int match_job_removed(sd_bus_message *m, void *userdata, sd_bus_error *error) {
3af0a96c 438 Context *c = userdata;
cf3872bd
YW
439 UnitStatusInfo *u;
440 const char *path;
441 unsigned n = 0;
3af0a96c
YW
442 int r;
443
444 assert(c);
445 assert(m);
446
447 r = sd_bus_message_read(m, "uoss", NULL, &path, NULL, NULL);
448 if (r < 0) {
449 bus_log_parse_error(r);
450 return 0;
451 }
452
cf3872bd
YW
453 LIST_FOREACH(units, u, c->units)
454 if (streq_ptr(path, u->path))
455 u->path = mfree(u->path);
456 else
457 n += !!u->path;
3af0a96c 458
cf3872bd 459 if (n == 0) {
cf3872bd 460 c->slot_job_removed = sd_bus_slot_unref(c->slot_job_removed);
49942d6b 461
ad7fb943
ZJS
462 (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m),
463 "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "NTP",
464 NULL);
cf3872bd 465 }
3af0a96c
YW
466
467 return 0;
468}
469
cf3872bd 470static int unit_start_or_stop(UnitStatusInfo *u, sd_bus *bus, sd_bus_error *error, bool start) {
3af0a96c 471 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
3af0a96c 472 const char *path;
c5f0532f
LP
473 int r;
474
5d280742 475 assert(u);
c5f0532f
LP
476 assert(bus);
477 assert(error);
478
43fe4f76 479 r = bus_call_method(
81b84399 480 bus,
43fe4f76 481 bus_systemd_mgr,
5d280742 482 start ? "StartUnit" : "StopUnit",
81b84399 483 error,
3af0a96c 484 &reply,
81b84399 485 "ss",
5d280742 486 u->name,
81b84399 487 "replace");
ad7fb943
ZJS
488 log_unit_full(u, r < 0 ? LOG_WARNING : LOG_DEBUG, r,
489 "%s unit: %m", start ? "Starting" : "Stopping");
5d280742 490 if (r < 0)
b72ddf0f 491 return r;
c5f0532f 492
3af0a96c
YW
493 r = sd_bus_message_read(reply, "o", &path);
494 if (r < 0)
495 return bus_log_parse_error(r);
496
cf3872bd 497 r = free_and_strdup(&u->path, path);
3af0a96c
YW
498 if (r < 0)
499 return log_oom();
500
b72ddf0f 501 return 0;
c5f0532f
LP
502}
503
5d280742 504static int unit_enable_or_disable(UnitStatusInfo *u, sd_bus *bus, sd_bus_error *error, bool enable) {
c5f0532f 505 int r;
c5f0532f 506
5d280742 507 assert(u);
c5f0532f
LP
508 assert(bus);
509 assert(error);
510
5d280742
YW
511 /* Call context_update_ntp_status() to update UnitStatusInfo before calling this. */
512
ad7fb943
ZJS
513 if (streq(u->unit_file_state, "enabled") == enable) {
514 log_unit_debug(u, "already %sd.", enable_disable(enable));
5d280742 515 return 0;
ad7fb943
ZJS
516 }
517
518 log_unit_info(u, "%s unit.", enable ? "Enabling" : "Disabling");
5d280742
YW
519
520 if (enable)
43fe4f76 521 r = bus_call_method(
40ca29a1 522 bus,
43fe4f76 523 bus_systemd_mgr,
b72ddf0f 524 "EnableUnitFiles",
40ca29a1
LP
525 error,
526 NULL,
b72ddf0f 527 "asbb", 1,
5d280742 528 u->name,
b72ddf0f
KS
529 false, true);
530 else
43fe4f76 531 r = bus_call_method(
b72ddf0f 532 bus,
43fe4f76 533 bus_systemd_mgr,
b72ddf0f
KS
534 "DisableUnitFiles",
535 error,
536 NULL,
537 "asb", 1,
5d280742 538 u->name,
b72ddf0f 539 false);
5d280742 540 if (r < 0)
b72ddf0f 541 return r;
c5f0532f 542
43fe4f76 543 r = bus_call_method(bus, bus_systemd_mgr, "Reload", error, NULL, NULL);
3af0a96c
YW
544 if (r < 0)
545 return r;
546
b72ddf0f 547 return 0;
40ca29a1 548}
c5f0532f 549
6cc379b5
YW
550static BUS_DEFINE_PROPERTY_GET_GLOBAL(property_get_time, "t", now(CLOCK_REALTIME));
551static BUS_DEFINE_PROPERTY_GET_GLOBAL(property_get_ntp_sync, "b", ntp_synced());
552
6fc60278
LP
553static int property_get_rtc_time(
554 sd_bus *bus,
555 const char *path,
556 const char *interface,
557 const char *property,
558 sd_bus_message *reply,
ebcf1f97
LP
559 void *userdata,
560 sd_bus_error *error) {
6fc60278 561
d9cb4bba
ZJS
562 struct tm tm = {};
563 usec_t t = 0;
6fc60278
LP
564 int r;
565
60989612 566 r = clock_get_hwclock(&tm);
d9cb4bba 567 if (r == -EBUSY)
07a062a7 568 log_warning("/dev/rtc is busy. Is somebody keeping it open continuously? That's not a good idea... Returning a bogus RTC timestamp.");
d9cb4bba 569 else if (r == -ENOENT)
07a062a7 570 log_debug("/dev/rtc not found.");
d9cb4bba 571 else if (r < 0)
10a87006 572 return sd_bus_error_set_errnof(error, r, "Failed to read RTC: %m");
ebcf1f97 573 else
2f6a5907 574 t = (usec_t) timegm(&tm) * USEC_PER_SEC;
6fc60278 575
ebcf1f97 576 return sd_bus_message_append(reply, "t", t);
6fc60278
LP
577}
578
5d280742
YW
579static int property_get_can_ntp(
580 sd_bus *bus,
581 const char *path,
582 const char *interface,
583 const char *property,
584 sd_bus_message *reply,
585 void *userdata,
586 sd_bus_error *error) {
587
588 Context *c = userdata;
589 int r;
590
591 assert(c);
592 assert(bus);
593 assert(property);
594 assert(reply);
595 assert(error);
596
b4356b57
YW
597 if (c->slot_job_removed)
598 /* When the previous request is not finished, then assume NTP is enabled. */
599 return sd_bus_message_append(reply, "b", true);
600
5d280742
YW
601 r = context_update_ntp_status(c, bus, reply);
602 if (r < 0)
603 return r;
604
605 return sd_bus_message_append(reply, "b", context_ntp_service_exists(c) > 0);
606}
607
608static int property_get_ntp(
609 sd_bus *bus,
610 const char *path,
611 const char *interface,
612 const char *property,
613 sd_bus_message *reply,
614 void *userdata,
615 sd_bus_error *error) {
616
617 Context *c = userdata;
618 int r;
619
620 assert(c);
621 assert(bus);
622 assert(property);
623 assert(reply);
624 assert(error);
625
b4356b57
YW
626 if (c->slot_job_removed)
627 /* When the previous request is not finished, then assume NTP is active. */
628 return sd_bus_message_append(reply, "b", true);
629
5d280742
YW
630 r = context_update_ntp_status(c, bus, reply);
631 if (r < 0)
632 return r;
633
634 return sd_bus_message_append(reply, "b", context_ntp_service_is_active(c) > 0);
635}
636
19070062 637static int method_set_timezone(sd_bus_message *m, void *userdata, sd_bus_error *error) {
40ca29a1 638 Context *c = userdata;
2c3def62 639 int interactive, r;
40ca29a1 640 const char *z;
c5f0532f 641
7e9cf16c
LP
642 assert(m);
643 assert(c);
644
40ca29a1
LP
645 r = sd_bus_message_read(m, "sb", &z, &interactive);
646 if (r < 0)
ebcf1f97 647 return r;
c5f0532f 648
089fb865 649 if (!timezone_is_valid(z, LOG_DEBUG))
5322db06 650 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid or not installed time zone '%s'", z);
ac7019f3 651
539a68e0 652 if (streq_ptr(z, c->zone))
df2d202e 653 return sd_bus_reply_method_return(m, NULL);
c5f0532f 654
c529695e
LP
655 r = bus_verify_polkit_async(
656 m,
657 CAP_SYS_TIME,
658 "org.freedesktop.timedate1.set-timezone",
403ed0e5 659 NULL,
c529695e
LP
660 interactive,
661 UID_INVALID,
662 &c->polkit_registry,
663 error);
40ca29a1 664 if (r < 0)
ebcf1f97 665 return r;
40ca29a1 666 if (r == 0)
6fc60278 667 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
6ffe5e37 668
539a68e0
YW
669 r = free_and_strdup(&c->zone, z);
670 if (r < 0)
671 return r;
672
40ca29a1
LP
673 /* 1. Write new configuration file */
674 r = context_write_data_timezone(c);
675 if (r < 0) {
da927ba9 676 log_error_errno(r, "Failed to set time zone: %m");
10a87006 677 return sd_bus_error_set_errnof(error, r, "Failed to set time zone: %m");
40ca29a1 678 }
6ffe5e37 679
8a50b96f
LP
680 /* 2. Make glibc notice the new timezone */
681 tzset();
682
683 /* 3. Tell the kernel our timezone */
2a7ff45f
LP
684 r = clock_set_timezone(NULL);
685 if (r < 0)
686 log_debug_errno(r, "Failed to tell kernel about timezone, ignoring: %m");
6ffe5e37 687
40ca29a1
LP
688 if (c->local_rtc) {
689 struct timespec ts;
e0f691e1 690 struct tm tm;
c5f0532f 691
8a50b96f 692 /* 4. Sync RTC from system clock, with the new delta */
40ca29a1 693 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
e0f691e1 694 assert_se(localtime_r(&ts.tv_sec, &tm));
2a7ff45f 695
e0f691e1 696 r = clock_set_hwclock(&tm);
2a7ff45f
LP
697 if (r < 0)
698 log_debug_errno(r, "Failed to sync time to hardware clock, ignoring: %m");
40ca29a1 699 }
c5f0532f 700
40ca29a1 701 log_struct(LOG_INFO,
2b044526 702 "MESSAGE_ID=" SD_MESSAGE_TIMEZONE_CHANGE_STR,
40ca29a1 703 "TIMEZONE=%s", c->zone,
8a50b96f
LP
704 "TIMEZONE_SHORTNAME=%s", tzname[daylight],
705 "DAYLIGHT=%i", daylight,
a1230ff9 706 LOG_MESSAGE("Changed time zone to '%s' (%s).", c->zone, tzname[daylight]));
c5f0532f 707
ad7fb943
ZJS
708 (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m),
709 "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "Timezone",
710 NULL);
c5f0532f 711
df2d202e 712 return sd_bus_reply_method_return(m, NULL);
c5f0532f
LP
713}
714
19070062 715static int method_set_local_rtc(sd_bus_message *m, void *userdata, sd_bus_error *error) {
102d8f81 716 int lrtc, fix_system, interactive;
40ca29a1
LP
717 Context *c = userdata;
718 struct timespec ts;
f401e48c
LP
719 int r;
720
40ca29a1
LP
721 assert(m);
722 assert(c);
f401e48c 723
40ca29a1
LP
724 r = sd_bus_message_read(m, "bbb", &lrtc, &fix_system, &interactive);
725 if (r < 0)
ebcf1f97 726 return r;
f401e48c 727
40ca29a1 728 if (lrtc == c->local_rtc)
df2d202e 729 return sd_bus_reply_method_return(m, NULL);
f401e48c 730
c529695e
LP
731 r = bus_verify_polkit_async(
732 m,
733 CAP_SYS_TIME,
734 "org.freedesktop.timedate1.set-local-rtc",
403ed0e5 735 NULL,
c529695e
LP
736 interactive,
737 UID_INVALID,
738 &c->polkit_registry,
739 error);
40ca29a1 740 if (r < 0)
ebcf1f97 741 return r;
40ca29a1
LP
742 if (r == 0)
743 return 1;
f401e48c 744
40ca29a1 745 c->local_rtc = lrtc;
f401e48c 746
40ca29a1
LP
747 /* 1. Write new configuration file */
748 r = context_write_data_local_rtc(c);
749 if (r < 0) {
ad7fb943
ZJS
750 log_error_errno(r, "Failed to set RTC to %s: %m", lrtc ? "local" : "UTC");
751 return sd_bus_error_set_errnof(error, r, "Failed to set RTC to %s: %m", lrtc ? "local" : "UTC");
40ca29a1 752 }
f401e48c 753
40ca29a1 754 /* 2. Tell the kernel our timezone */
2a7ff45f
LP
755 r = clock_set_timezone(NULL);
756 if (r < 0)
757 log_debug_errno(r, "Failed to tell kernel about timezone, ignoring: %m");
f401e48c 758
40ca29a1
LP
759 /* 3. Synchronize clocks */
760 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
f401e48c 761
40ca29a1
LP
762 if (fix_system) {
763 struct tm tm;
f401e48c 764
2a7ff45f 765 /* Sync system clock from RTC; first, initialize the timezone fields of struct tm. */
40ca29a1 766 if (c->local_rtc)
e46acb79 767 localtime_r(&ts.tv_sec, &tm);
40ca29a1 768 else
e46acb79 769 gmtime_r(&ts.tv_sec, &tm);
72edcff5 770
2a7ff45f
LP
771 /* Override the main fields of struct tm, but not the timezone fields */
772 r = clock_get_hwclock(&tm);
773 if (r < 0)
774 log_debug_errno(r, "Failed to get hardware clock, ignoring: %m");
775 else {
776 /* And set the system clock with this */
40ca29a1
LP
777 if (c->local_rtc)
778 ts.tv_sec = mktime(&tm);
779 else
780 ts.tv_sec = timegm(&tm);
2076cf88 781
2a7ff45f
LP
782 if (clock_settime(CLOCK_REALTIME, &ts) < 0)
783 log_debug_errno(errno, "Failed to update system clock, ignoring: %m");
f401e48c
LP
784 }
785
40ca29a1 786 } else {
e46acb79 787 struct tm tm;
f401e48c 788
40ca29a1
LP
789 /* Sync RTC from system clock */
790 if (c->local_rtc)
e46acb79 791 localtime_r(&ts.tv_sec, &tm);
40ca29a1 792 else
e46acb79 793 gmtime_r(&ts.tv_sec, &tm);
2076cf88 794
e46acb79 795 r = clock_set_hwclock(&tm);
2a7ff45f
LP
796 if (r < 0)
797 log_debug_errno(r, "Failed to sync time to hardware clock, ignoring: %m");
40ca29a1 798 }
2076cf88 799
40ca29a1 800 log_info("RTC configured to %s time.", c->local_rtc ? "local" : "UTC");
2076cf88 801
ad7fb943
ZJS
802 (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m),
803 "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "LocalRTC",
804 NULL);
2076cf88 805
df2d202e 806 return sd_bus_reply_method_return(m, NULL);
40ca29a1 807}
2076cf88 808
19070062 809static int method_set_time(sd_bus_message *m, void *userdata, sd_bus_error *error) {
5d280742
YW
810 sd_bus *bus = sd_bus_message_get_bus(m);
811 int relative, interactive, r;
40ca29a1
LP
812 Context *c = userdata;
813 int64_t utc;
814 struct timespec ts;
2479df30 815 usec_t start;
e46acb79 816 struct tm tm;
2076cf88 817
40ca29a1
LP
818 assert(m);
819 assert(c);
2076cf88 820
b4356b57
YW
821 if (c->slot_job_removed)
822 return sd_bus_error_set(error, BUS_ERROR_AUTOMATIC_TIME_SYNC_ENABLED, "Previous request is not finished, refusing.");
823
5d280742
YW
824 r = context_update_ntp_status(c, bus, m);
825 if (r < 0)
826 return sd_bus_error_set_errnof(error, r, "Failed to update context: %m");
827
828 if (context_ntp_service_is_active(c) > 0)
e9e5ea88 829 return sd_bus_error_set(error, BUS_ERROR_AUTOMATIC_TIME_SYNC_ENABLED, "Automatic time synchronization is enabled");
82d115d9 830
6829cec4
SL
831 /* this only gets used if dbus does not provide a timestamp */
832 start = now(CLOCK_MONOTONIC);
833
40ca29a1
LP
834 r = sd_bus_message_read(m, "xbb", &utc, &relative, &interactive);
835 if (r < 0)
ebcf1f97 836 return r;
2076cf88 837
40ca29a1 838 if (!relative && utc <= 0)
e9e5ea88 839 return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid absolute time");
2076cf88 840
40ca29a1 841 if (relative && utc == 0)
df2d202e 842 return sd_bus_reply_method_return(m, NULL);
2076cf88 843
40ca29a1
LP
844 if (relative) {
845 usec_t n, x;
f401e48c 846
40ca29a1
LP
847 n = now(CLOCK_REALTIME);
848 x = n + utc;
f401e48c 849
40ca29a1
LP
850 if ((utc > 0 && x < n) ||
851 (utc < 0 && x > n))
e9e5ea88 852 return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Time value overflow");
f401e48c 853
40ca29a1
LP
854 timespec_store(&ts, x);
855 } else
856 timespec_store(&ts, (usec_t) utc);
2076cf88 857
c529695e
LP
858 r = bus_verify_polkit_async(
859 m,
860 CAP_SYS_TIME,
861 "org.freedesktop.timedate1.set-time",
403ed0e5 862 NULL,
c529695e
LP
863 interactive,
864 UID_INVALID,
865 &c->polkit_registry,
866 error);
40ca29a1 867 if (r < 0)
ebcf1f97 868 return r;
40ca29a1
LP
869 if (r == 0)
870 return 1;
871
2479df30
SL
872 /* adjust ts for time spent in program */
873 r = sd_bus_message_get_monotonic_usec(m, &start);
6829cec4 874 /* when sd_bus_message_get_monotonic_usec() returns -ENODATA it does not modify &start */
2479df30
SL
875 if (r < 0 && r != -ENODATA)
876 return r;
6829cec4
SL
877
878 timespec_store(&ts, timespec_load(&ts) + (now(CLOCK_MONOTONIC) - start));
2479df30 879
40ca29a1
LP
880 /* Set system clock */
881 if (clock_settime(CLOCK_REALTIME, &ts) < 0) {
56f64d95 882 log_error_errno(errno, "Failed to set local time: %m");
ebcf1f97 883 return sd_bus_error_set_errnof(error, errno, "Failed to set local time: %m");
40ca29a1 884 }
2076cf88 885
40ca29a1
LP
886 /* Sync down to RTC */
887 if (c->local_rtc)
e46acb79 888 localtime_r(&ts.tv_sec, &tm);
40ca29a1 889 else
e46acb79 890 gmtime_r(&ts.tv_sec, &tm);
2a7ff45f 891
e46acb79 892 r = clock_set_hwclock(&tm);
2a7ff45f
LP
893 if (r < 0)
894 log_debug_errno(r, "Failed to update hardware clock, ignoring: %m");
f401e48c 895
40ca29a1 896 log_struct(LOG_INFO,
2b044526 897 "MESSAGE_ID=" SD_MESSAGE_TIME_CHANGE_STR,
de0671ee 898 "REALTIME="USEC_FMT, timespec_load(&ts),
a1230ff9 899 LOG_MESSAGE("Changed local time to %s", ctime(&ts.tv_sec)));
f401e48c 900
df2d202e 901 return sd_bus_reply_method_return(m, NULL);
40ca29a1 902}
f401e48c 903
19070062 904static int method_set_ntp(sd_bus_message *m, void *userdata, sd_bus_error *error) {
cf3872bd 905 _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot = NULL;
5d280742 906 sd_bus *bus = sd_bus_message_get_bus(m);
40ca29a1 907 Context *c = userdata;
5d280742 908 UnitStatusInfo *u;
ad7fb943 909 const UnitStatusInfo *selected = NULL;
5d280742 910 int enable, interactive, q, r;
f401e48c 911
19070062 912 assert(m);
5d280742 913 assert(bus);
19070062
LP
914 assert(c);
915
5d280742 916 r = sd_bus_message_read(m, "bb", &enable, &interactive);
40ca29a1 917 if (r < 0)
ebcf1f97 918 return r;
f401e48c 919
5d280742
YW
920 r = context_update_ntp_status(c, bus, m);
921 if (r < 0)
922 return r;
923
924 if (context_ntp_service_exists(c) <= 0)
925 return sd_bus_error_set(error, BUS_ERROR_NO_NTP_SUPPORT, "NTP not supported");
f401e48c 926
c529695e
LP
927 r = bus_verify_polkit_async(
928 m,
929 CAP_SYS_TIME,
930 "org.freedesktop.timedate1.set-ntp",
403ed0e5 931 NULL,
c529695e
LP
932 interactive,
933 UID_INVALID,
934 &c->polkit_registry,
935 error);
40ca29a1 936 if (r < 0)
ebcf1f97 937 return r;
40ca29a1
LP
938 if (r == 0)
939 return 1;
f401e48c 940
cf3872bd
YW
941 /* This method may be called frequently. Forget the previous job if it has not completed yet. */
942 LIST_FOREACH(units, u, c->units)
943 u->path = mfree(u->path);
944
945 if (!c->slot_job_removed) {
43fe4f76 946 r = bus_match_signal_async(
cf3872bd
YW
947 bus,
948 &slot,
43fe4f76 949 bus_systemd_mgr,
cf3872bd
YW
950 "JobRemoved",
951 match_job_removed, NULL, c);
952 if (r < 0)
953 return r;
954 }
955
0957790b 956 if (enable)
5d280742 957 LIST_FOREACH(units, u, c->units) {
0957790b 958 bool enable_this_one = !selected;
5d280742 959
5d280742
YW
960 if (!streq(u->load_state, "loaded"))
961 continue;
962
0957790b 963 r = unit_enable_or_disable(u, bus, error, enable_this_one);
5d280742 964 if (r < 0)
0957790b
ZJS
965 /* If enablement failed, don't start this unit. */
966 enable_this_one = false;
5d280742 967
0957790b
ZJS
968 r = unit_start_or_stop(u, bus, error, enable_this_one);
969 if (r < 0)
970 log_unit_warning_errno(u, r, "Failed to %s %sd NTP unit, ignoring: %m",
971 enable_this_one ? "start" : "stop",
972 enable_disable(enable_this_one));
973 if (enable_this_one)
974 selected = u;
5d280742 975 }
3af0a96c 976 else
5d280742 977 LIST_FOREACH(units, u, c->units) {
0957790b 978 if (!streq(u->load_state, "loaded"))
5d280742
YW
979 continue;
980
0957790b
ZJS
981 q = unit_enable_or_disable(u, bus, error, false);
982 if (q < 0)
983 r = q;
984
985 q = unit_start_or_stop(u, bus, error, false);
986 if (q < 0)
987 r = q;
5d280742 988 }
40ca29a1 989
40ca29a1 990 if (r < 0)
ebcf1f97 991 return r;
ad7fb943
ZJS
992 if (enable && !selected)
993 return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "No NTP service found to enable.");
f401e48c 994
cf3872bd
YW
995 if (slot)
996 c->slot_job_removed = TAKE_PTR(slot);
997
ad7fb943
ZJS
998 if (selected)
999 log_info("Set NTP to enabled (%s).", selected->name);
1000 else
1001 log_info("Set NTP to disabled.");
f401e48c 1002
df2d202e 1003 return sd_bus_reply_method_return(m, NULL);
f401e48c
LP
1004}
1005
2cf0b2fe
NT
1006static int method_list_timezones(sd_bus_message *m, void *userdata, sd_bus_error *error) {
1007 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
1008 _cleanup_strv_free_ char **zones = NULL;
1009 int r;
1010
1011 assert(m);
1012
1013 r = get_timezones(&zones);
1014 if (r < 0)
1015 return sd_bus_error_set_errnof(error, r, "Failed to read list of time zones: %m");
1016
1017 r = sd_bus_message_new_method_return(m, &reply);
1018 if (r < 0)
1019 return r;
1020
1021 r = sd_bus_message_append_strv(reply, zones);
1022 if (r < 0)
1023 return r;
1024
1025 return sd_bus_send(NULL, reply, NULL);
1026}
1027
40ca29a1
LP
1028static const sd_bus_vtable timedate_vtable[] = {
1029 SD_BUS_VTABLE_START(0),
599c99ee 1030
40ca29a1 1031 SD_BUS_PROPERTY("Timezone", "s", NULL, offsetof(Context, zone), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
82d115d9 1032 SD_BUS_PROPERTY("LocalRTC", "b", bus_property_get_bool, offsetof(Context, local_rtc), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
5d280742
YW
1033 SD_BUS_PROPERTY("CanNTP", "b", property_get_can_ntp, 0, 0),
1034 SD_BUS_PROPERTY("NTP", "b", property_get_ntp, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
03cc26dd
LP
1035 SD_BUS_PROPERTY("NTPSynchronized", "b", property_get_ntp_sync, 0, 0),
1036 SD_BUS_PROPERTY("TimeUSec", "t", property_get_time, 0, 0),
6fc60278 1037 SD_BUS_PROPERTY("RTCTimeUSec", "t", property_get_rtc_time, 0, 0),
599c99ee
ZJS
1038
1039 SD_BUS_METHOD_WITH_NAMES("SetTime",
1040 "xbb",
1041 SD_BUS_PARAM(usec_utc)
1042 SD_BUS_PARAM(relative)
1043 SD_BUS_PARAM(interactive),
1044 NULL,,
1045 method_set_time,
1046 SD_BUS_VTABLE_UNPRIVILEGED),
1047 SD_BUS_METHOD_WITH_NAMES("SetTimezone",
1048 "sb",
1049 SD_BUS_PARAM(timezone)
1050 SD_BUS_PARAM(interactive),
1051 NULL,,
1052 method_set_timezone,
1053 SD_BUS_VTABLE_UNPRIVILEGED),
1054 SD_BUS_METHOD_WITH_NAMES("SetLocalRTC",
1055 "bbb",
1056 SD_BUS_PARAM(local_rtc)
1057 SD_BUS_PARAM(fix_system)
1058 SD_BUS_PARAM(interactive),
1059 NULL,,
1060 method_set_local_rtc,
1061 SD_BUS_VTABLE_UNPRIVILEGED),
1062 SD_BUS_METHOD_WITH_NAMES("SetNTP",
1063 "bb",
1064 SD_BUS_PARAM(use_ntp)
1065 SD_BUS_PARAM(interactive),
1066 NULL,,
1067 method_set_ntp,
1068 SD_BUS_VTABLE_UNPRIVILEGED),
1069 SD_BUS_METHOD_WITH_NAMES("ListTimezones",
1070 NULL,,
1071 "as",
1072 SD_BUS_PARAM(timezones),
1073 method_list_timezones,
1074 SD_BUS_VTABLE_UNPRIVILEGED),
1075
40ca29a1
LP
1076 SD_BUS_VTABLE_END,
1077};
1078
c4b7d95c
ZJS
1079const BusObjectImplementation manager_object = {
1080 "/org/freedesktop/timedate1",
1081 "org.freedesktop.timedate1",
1082 .vtables = BUS_VTABLES(timedate_vtable),
1083};
1084
40ca29a1 1085static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
4afd3348 1086 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
f401e48c
LP
1087 int r;
1088
40ca29a1
LP
1089 assert(c);
1090 assert(event);
f401e48c
LP
1091 assert(_bus);
1092
76b54375 1093 r = sd_bus_default_system(&bus);
f647962d
MS
1094 if (r < 0)
1095 return log_error_errno(r, "Failed to get system bus connection: %m");
f401e48c 1096
c4b7d95c 1097 r = bus_add_implementation(bus, &manager_object, c);
f647962d 1098 if (r < 0)
c4b7d95c 1099 return r;
f401e48c 1100
ac9f55ed
LP
1101 r = bus_log_control_api_register(bus);
1102 if (r < 0)
1103 return r;
1104
0c0b9306 1105 r = sd_bus_request_name_async(bus, NULL, "org.freedesktop.timedate1", 0, NULL, NULL);
f647962d 1106 if (r < 0)
0c0b9306 1107 return log_error_errno(r, "Failed to request name: %m");
add10b5a 1108
40ca29a1 1109 r = sd_bus_attach_event(bus, event, 0);
f647962d
MS
1110 if (r < 0)
1111 return log_error_errno(r, "Failed to attach bus to event loop: %m");
f401e48c 1112
1cc6c93a 1113 *_bus = TAKE_PTR(bus);
f401e48c 1114
40ca29a1 1115 return 0;
f401e48c
LP
1116}
1117
1f47bc33
YW
1118static int run(int argc, char *argv[]) {
1119 _cleanup_(context_clear) Context context = {};
4afd3348
LP
1120 _cleanup_(sd_event_unrefp) sd_event *event = NULL;
1121 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
f401e48c 1122 int r;
f401e48c 1123
6bf3c61c 1124 log_setup_service();
f401e48c 1125
fc021a5b
ZJS
1126 r = service_parse_argv("systemd-timedated.service",
1127 "Manage the system clock and timezone and NTP enablement.",
c4b7d95c
ZJS
1128 BUS_IMPLEMENTATIONS(&manager_object,
1129 &log_control_object),
fc021a5b
ZJS
1130 argc, argv);
1131 if (r <= 0)
1132 return r;
4c12626c 1133
fc021a5b 1134 umask(0022);
f401e48c 1135
754f0269
YW
1136 assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0);
1137
afc6adb5 1138 r = sd_event_default(&event);
1f47bc33
YW
1139 if (r < 0)
1140 return log_error_errno(r, "Failed to allocate event loop: %m");
f401e48c 1141
754f0269
YW
1142 (void) sd_event_set_watchdog(event, true);
1143
1144 r = sd_event_add_signal(event, NULL, SIGINT, NULL, NULL);
1f47bc33
YW
1145 if (r < 0)
1146 return log_error_errno(r, "Failed to install SIGINT handler: %m");
754f0269
YW
1147
1148 r = sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL);
1f47bc33
YW
1149 if (r < 0)
1150 return log_error_errno(r, "Failed to install SIGTERM handler: %m");
cde93897 1151
40ca29a1 1152 r = connect_bus(&context, event, &bus);
f401e48c 1153 if (r < 0)
1f47bc33 1154 return r;
f401e48c 1155
dc751688 1156 (void) sd_bus_negotiate_timestamp(bus, true);
2479df30 1157
40ca29a1 1158 r = context_read_data(&context);
1f47bc33
YW
1159 if (r < 0)
1160 return log_error_errno(r, "Failed to read time zone data: %m");
c5f0532f 1161
5d280742
YW
1162 r = context_parse_ntp_services(&context);
1163 if (r < 0)
1f47bc33 1164 return r;
ad740100 1165
37224a5f 1166 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.timedate1", DEFAULT_EXIT_USEC, NULL, NULL);
1f47bc33
YW
1167 if (r < 0)
1168 return log_error_errno(r, "Failed to run event loop: %m");
f401e48c 1169
1f47bc33 1170 return 0;
f401e48c 1171}
1f47bc33
YW
1172
1173DEFINE_MAIN_FUNCTION(run);