]>
Commit | Line | Data |
---|---|---|
f401e48c LP |
1 | /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ |
2 | ||
3 | /*** | |
4 | This file is part of systemd. | |
5 | ||
6 | Copyright 2011 Lennart Poettering | |
7 | ||
8 | systemd is free software; you can redistribute it and/or modify it | |
9 | under the terms of the GNU General Public License as published by | |
10 | the Free Software Foundation; either version 2 of the License, or | |
11 | (at your option) any later version. | |
12 | ||
13 | systemd is distributed in the hope that it will be useful, but | |
14 | WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
16 | General Public License for more details. | |
17 | ||
18 | You should have received a copy of the GNU General Public License | |
19 | along with systemd; If not, see <http://www.gnu.org/licenses/>. | |
20 | ***/ | |
21 | ||
22 | #include <dbus/dbus.h> | |
23 | ||
24 | #include <errno.h> | |
25 | #include <string.h> | |
26 | #include <unistd.h> | |
27 | ||
28 | #include "util.h" | |
29 | #include "strv.h" | |
30 | #include "dbus-common.h" | |
31 | #include "polkit.h" | |
ad740100 | 32 | #include "def.h" |
f401e48c LP |
33 | |
34 | #define NULL_ADJTIME_UTC "0.0 0 0\n0\nUTC\n" | |
35 | #define NULL_ADJTIME_LOCAL "0.0 0 0\n0\nLOCAL\n" | |
36 | ||
91f9dcaf | 37 | #define INTERFACE \ |
f401e48c LP |
38 | " <interface name=\"org.freedesktop.timedate1\">\n" \ |
39 | " <property name=\"Timezone\" type=\"s\" access=\"read\"/>\n" \ | |
40 | " <property name=\"LocalRTC\" type=\"b\" access=\"read\"/>\n" \ | |
c5f0532f | 41 | " <property name=\"NTP\" type=\"b\" access=\"read\"/>\n" \ |
f401e48c LP |
42 | " <method name=\"SetTime\">\n" \ |
43 | " <arg name=\"usec_utc\" type=\"x\" direction=\"in\"/>\n" \ | |
44 | " <arg name=\"relative\" type=\"b\" direction=\"in\"/>\n" \ | |
45 | " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \ | |
46 | " </method>\n" \ | |
47 | " <method name=\"SetTimezone\">\n" \ | |
48 | " <arg name=\"timezone\" type=\"s\" direction=\"in\"/>\n" \ | |
49 | " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \ | |
50 | " </method>\n" \ | |
51 | " <method name=\"SetLocalRTC\">\n" \ | |
52 | " <arg name=\"local_rtc\" type=\"b\" direction=\"in\"/>\n" \ | |
91f9dcaf | 53 | " <arg name=\"fix_system\" type=\"b\" direction=\"in\"/>\n" \ |
f401e48c LP |
54 | " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \ |
55 | " </method>\n" \ | |
c5f0532f LP |
56 | " <method name=\"SetNTP\">\n" \ |
57 | " <arg name=\"use_ntp\" type=\"b\" direction=\"in\"/>\n" \ | |
58 | " <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \ | |
59 | " </method>\n" \ | |
91f9dcaf LP |
60 | " </interface>\n" |
61 | ||
62 | #define INTROSPECTION \ | |
63 | DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ | |
64 | "<node>\n" \ | |
65 | INTERFACE \ | |
f401e48c LP |
66 | BUS_PROPERTIES_INTERFACE \ |
67 | BUS_INTROSPECTABLE_INTERFACE \ | |
68 | BUS_PEER_INTERFACE \ | |
69 | "</node>\n" | |
70 | ||
71 | #define INTERFACES_LIST \ | |
72 | BUS_GENERIC_INTERFACES_LIST \ | |
3417e2c3 | 73 | "org.freedesktop.timedate1\0" |
f401e48c | 74 | |
91f9dcaf LP |
75 | const char timedate_interface[] _introspect_("timedate1") = INTERFACE; |
76 | ||
d200735e MS |
77 | typedef struct TZ { |
78 | char *zone; | |
79 | bool local_rtc; | |
80 | int use_ntp; | |
81 | } TZ; | |
f401e48c | 82 | |
d200735e MS |
83 | static TZ tz = { |
84 | .use_ntp = -1, | |
85 | }; | |
86 | ||
87 | static usec_t remain_until; | |
ad740100 | 88 | |
f401e48c | 89 | static void free_data(void) { |
d200735e MS |
90 | free(tz.zone); |
91 | tz.zone = NULL; | |
f401e48c | 92 | |
d200735e | 93 | tz.local_rtc = false; |
f401e48c LP |
94 | } |
95 | ||
96 | static bool valid_timezone(const char *name) { | |
97 | const char *p; | |
98 | char *t; | |
99 | bool slash = false; | |
100 | int r; | |
101 | struct stat st; | |
102 | ||
103 | assert(name); | |
104 | ||
105 | if (*name == '/' || *name == 0) | |
106 | return false; | |
107 | ||
108 | for (p = name; *p; p++) { | |
109 | if (!(*p >= '0' && *p <= '9') && | |
110 | !(*p >= 'a' && *p <= 'z') && | |
111 | !(*p >= 'A' && *p <= 'Z') && | |
112 | !(*p == '-' || *p == '_' || *p == '+' || *p == '/')) | |
113 | return false; | |
114 | ||
115 | if (*p == '/') { | |
116 | ||
117 | if (slash) | |
118 | return false; | |
119 | ||
120 | slash = true; | |
121 | } else | |
122 | slash = false; | |
123 | } | |
124 | ||
125 | if (slash) | |
126 | return false; | |
127 | ||
128 | t = strappend("/usr/share/zoneinfo/", name); | |
129 | if (!t) | |
130 | return false; | |
131 | ||
132 | r = stat(t, &st); | |
133 | free(t); | |
134 | ||
135 | if (r < 0) | |
136 | return false; | |
137 | ||
138 | if (!S_ISREG(st.st_mode)) | |
139 | return false; | |
140 | ||
141 | return true; | |
142 | } | |
143 | ||
144 | static void verify_timezone(void) { | |
145 | char *p, *a = NULL, *b = NULL; | |
146 | size_t l, q; | |
147 | int j, k; | |
148 | ||
d200735e | 149 | if (!tz.zone) |
f401e48c LP |
150 | return; |
151 | ||
d200735e | 152 | p = strappend("/usr/share/zoneinfo/", tz.zone); |
f401e48c LP |
153 | if (!p) { |
154 | log_error("Out of memory"); | |
155 | return; | |
156 | } | |
157 | ||
158 | j = read_full_file("/etc/localtime", &a, &l); | |
159 | k = read_full_file(p, &b, &q); | |
160 | ||
161 | free(p); | |
162 | ||
163 | if (j < 0 || k < 0 || l != q || memcmp(a, b, l)) { | |
164 | log_warning("/etc/localtime and /etc/timezone out of sync."); | |
d200735e MS |
165 | free(tz.zone); |
166 | tz.zone = NULL; | |
f401e48c LP |
167 | } |
168 | ||
169 | free(a); | |
170 | free(b); | |
171 | } | |
172 | ||
173 | static int read_data(void) { | |
174 | int r; | |
f401e48c LP |
175 | |
176 | free_data(); | |
177 | ||
d200735e | 178 | r = read_one_line_file("/etc/timezone", &tz.zone); |
a724d2ed LP |
179 | if (r < 0) { |
180 | if (r != -ENOENT) | |
181 | log_warning("Failed to read /etc/timezone: %s", strerror(-r)); | |
182 | ||
183 | #ifdef TARGET_FEDORA | |
184 | r = parse_env_file("/etc/sysconfig/clock", NEWLINE, | |
d200735e | 185 | "ZONE", &tz.zone, |
a724d2ed LP |
186 | NULL); |
187 | ||
188 | if (r < 0 && r != -ENOENT) | |
189 | log_warning("Failed to read /etc/sysconfig/clock: %s", strerror(-r)); | |
190 | #endif | |
191 | } | |
192 | ||
d200735e MS |
193 | if (isempty(tz.zone)) { |
194 | free(tz.zone); | |
195 | tz.zone = NULL; | |
a724d2ed | 196 | } |
f401e48c LP |
197 | |
198 | verify_timezone(); | |
199 | ||
d200735e | 200 | tz.local_rtc = hwclock_is_localtime() > 0; |
f401e48c LP |
201 | |
202 | return 0; | |
203 | } | |
204 | ||
205 | static int write_data_timezone(void) { | |
206 | int r = 0; | |
207 | char *p; | |
208 | ||
d200735e | 209 | if (!tz.zone) { |
f401e48c LP |
210 | if (unlink("/etc/timezone") < 0 && errno != ENOENT) |
211 | r = -errno; | |
212 | ||
213 | if (unlink("/etc/localtime") < 0 && errno != ENOENT) | |
214 | r = -errno; | |
215 | ||
216 | return r; | |
217 | } | |
218 | ||
d200735e | 219 | p = strappend("/usr/share/zoneinfo/", tz.zone); |
f401e48c LP |
220 | if (!p) { |
221 | log_error("Out of memory"); | |
222 | return -ENOMEM; | |
223 | } | |
224 | ||
225 | r = symlink_or_copy_atomic(p, "/etc/localtime"); | |
226 | free(p); | |
227 | ||
228 | if (r < 0) | |
229 | return r; | |
230 | ||
d200735e | 231 | r = write_one_line_file_atomic("/etc/timezone", tz.zone); |
f401e48c LP |
232 | if (r < 0) |
233 | return r; | |
234 | ||
235 | return 0; | |
236 | } | |
237 | ||
238 | static int write_data_local_rtc(void) { | |
239 | int r; | |
240 | char *s, *w; | |
241 | ||
242 | r = read_full_file("/etc/adjtime", &s, NULL); | |
243 | if (r < 0) { | |
244 | if (r != -ENOENT) | |
245 | return r; | |
246 | ||
d200735e | 247 | if (!tz.local_rtc) |
f401e48c LP |
248 | return 0; |
249 | ||
250 | w = strdup(NULL_ADJTIME_LOCAL); | |
251 | if (!w) | |
252 | return -ENOMEM; | |
253 | } else { | |
254 | char *p, *e; | |
255 | size_t a, b; | |
256 | ||
257 | p = strchr(s, '\n'); | |
258 | if (!p) { | |
259 | free(s); | |
260 | return -EIO; | |
261 | } | |
262 | ||
263 | p = strchr(p+1, '\n'); | |
264 | if (!p) { | |
265 | free(s); | |
266 | return -EIO; | |
267 | } | |
268 | ||
269 | p++; | |
270 | e = strchr(p, '\n'); | |
8ea913b2 | 271 | if (!e) { |
f401e48c LP |
272 | free(s); |
273 | return -EIO; | |
274 | } | |
275 | ||
276 | a = p - s; | |
277 | b = strlen(e); | |
278 | ||
d200735e | 279 | w = new(char, a + (tz.local_rtc ? 5 : 3) + b + 1); |
f401e48c LP |
280 | if (!w) { |
281 | free(s); | |
282 | return -ENOMEM; | |
283 | } | |
284 | ||
d200735e | 285 | *(char*) mempcpy(stpcpy(mempcpy(w, s, a), tz.local_rtc ? "LOCAL" : "UTC"), e, b) = 0; |
f401e48c LP |
286 | |
287 | if (streq(w, NULL_ADJTIME_UTC)) { | |
288 | free(w); | |
289 | ||
290 | if (unlink("/etc/adjtime") < 0) { | |
291 | if (errno != ENOENT) | |
292 | return -errno; | |
293 | } | |
294 | ||
295 | return 0; | |
296 | } | |
297 | } | |
298 | ||
299 | r = write_one_line_file_atomic("/etc/adjtime", w); | |
300 | free(w); | |
301 | ||
302 | return r; | |
303 | } | |
304 | ||
c5f0532f LP |
305 | static int read_ntp(DBusConnection *bus) { |
306 | DBusMessage *m = NULL, *reply = NULL; | |
307 | const char *name = "ntpd.service", *s; | |
308 | DBusError error; | |
309 | int r; | |
310 | ||
311 | assert(bus); | |
312 | ||
313 | dbus_error_init(&error); | |
314 | ||
315 | m = dbus_message_new_method_call( | |
316 | "org.freedesktop.systemd1", | |
317 | "/org/freedesktop/systemd1", | |
318 | "org.freedesktop.systemd1.Manager", | |
319 | "GetUnitFileState"); | |
320 | ||
321 | if (!m) { | |
322 | log_error("Out of memory"); | |
323 | r = -ENOMEM; | |
324 | goto finish; | |
325 | } | |
326 | ||
327 | if (!dbus_message_append_args(m, | |
328 | DBUS_TYPE_STRING, &name, | |
329 | DBUS_TYPE_INVALID)) { | |
330 | log_error("Could not append arguments to message."); | |
331 | r = -ENOMEM; | |
332 | goto finish; | |
333 | } | |
334 | ||
335 | reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error); | |
336 | if (!reply) { | |
2aa4c315 LP |
337 | |
338 | if (streq(error.name, "org.freedesktop.DBus.Error.FileNotFound")) { | |
339 | /* NTP is not installed. */ | |
340 | tz.use_ntp = false; | |
341 | r = 0; | |
342 | goto finish; | |
343 | } | |
344 | ||
c5f0532f LP |
345 | log_error("Failed to issue method call: %s", bus_error_message(&error)); |
346 | r = -EIO; | |
347 | goto finish; | |
348 | } | |
349 | ||
350 | if (!dbus_message_get_args(reply, &error, | |
351 | DBUS_TYPE_STRING, &s, | |
352 | DBUS_TYPE_INVALID)) { | |
353 | log_error("Failed to parse reply: %s", bus_error_message(&error)); | |
354 | r = -EIO; | |
355 | goto finish; | |
356 | } | |
357 | ||
d200735e | 358 | tz.use_ntp = |
c5f0532f LP |
359 | streq(s, "enabled") || |
360 | streq(s, "enabled-runtime"); | |
361 | r = 0; | |
362 | ||
363 | finish: | |
364 | if (m) | |
365 | dbus_message_unref(m); | |
366 | ||
367 | if (reply) | |
368 | dbus_message_unref(reply); | |
369 | ||
370 | dbus_error_free(&error); | |
371 | ||
372 | return r; | |
373 | } | |
374 | ||
375 | static int start_ntp(DBusConnection *bus, DBusError *error) { | |
376 | DBusMessage *m = NULL, *reply = NULL; | |
377 | const char *name = "ntpd.service", *mode = "replace"; | |
378 | int r; | |
379 | ||
380 | assert(bus); | |
381 | assert(error); | |
382 | ||
383 | m = dbus_message_new_method_call( | |
384 | "org.freedesktop.systemd1", | |
385 | "/org/freedesktop/systemd1", | |
386 | "org.freedesktop.systemd1.Manager", | |
d200735e | 387 | tz.use_ntp ? "StartUnit" : "StopUnit"); |
c5f0532f LP |
388 | if (!m) { |
389 | log_error("Could not allocate message."); | |
390 | r = -ENOMEM; | |
391 | goto finish; | |
392 | } | |
393 | ||
394 | if (!dbus_message_append_args(m, | |
395 | DBUS_TYPE_STRING, &name, | |
396 | DBUS_TYPE_STRING, &mode, | |
397 | DBUS_TYPE_INVALID)) { | |
398 | log_error("Could not append arguments to message."); | |
399 | r = -ENOMEM; | |
400 | goto finish; | |
401 | } | |
402 | ||
403 | reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error); | |
404 | if (!reply) { | |
405 | log_error("Failed to issue method call: %s", bus_error_message(error)); | |
406 | r = -EIO; | |
407 | goto finish; | |
408 | } | |
409 | ||
410 | r = 0; | |
411 | ||
412 | finish: | |
413 | if (m) | |
414 | dbus_message_unref(m); | |
415 | ||
416 | if (reply) | |
417 | dbus_message_unref(reply); | |
418 | ||
419 | return r; | |
420 | } | |
421 | ||
422 | static int enable_ntp(DBusConnection *bus, DBusError *error) { | |
423 | DBusMessage *m = NULL, *reply = NULL; | |
424 | const char * const names[] = { "ntpd.service", NULL }; | |
425 | int r; | |
426 | DBusMessageIter iter; | |
427 | dbus_bool_t f = FALSE, t = TRUE; | |
428 | ||
429 | assert(bus); | |
430 | assert(error); | |
431 | ||
432 | m = dbus_message_new_method_call( | |
433 | "org.freedesktop.systemd1", | |
434 | "/org/freedesktop/systemd1", | |
435 | "org.freedesktop.systemd1.Manager", | |
d200735e | 436 | tz.use_ntp ? "EnableUnitFiles" : "DisableUnitFiles"); |
c5f0532f LP |
437 | |
438 | if (!m) { | |
439 | log_error("Could not allocate message."); | |
440 | r = -ENOMEM; | |
441 | goto finish; | |
442 | } | |
443 | ||
444 | dbus_message_iter_init_append(m, &iter); | |
445 | ||
446 | r = bus_append_strv_iter(&iter, (char**) names); | |
447 | if (r < 0) { | |
448 | log_error("Failed to append unit files."); | |
449 | goto finish; | |
450 | } | |
451 | /* send runtime bool */ | |
452 | if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &f)) { | |
453 | log_error("Failed to append runtime boolean."); | |
454 | r = -ENOMEM; | |
455 | goto finish; | |
456 | } | |
457 | ||
d200735e | 458 | if (tz.use_ntp) { |
c5f0532f LP |
459 | /* send force bool */ |
460 | if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &t)) { | |
461 | log_error("Failed to append force boolean."); | |
462 | r = -ENOMEM; | |
463 | goto finish; | |
464 | } | |
465 | } | |
466 | ||
467 | reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error); | |
468 | if (!reply) { | |
469 | log_error("Failed to issue method call: %s", bus_error_message(error)); | |
470 | r = -EIO; | |
471 | goto finish; | |
472 | } | |
473 | ||
474 | dbus_message_unref(m); | |
475 | m = dbus_message_new_method_call( | |
476 | "org.freedesktop.systemd1", | |
477 | "/org/freedesktop/systemd1", | |
478 | "org.freedesktop.systemd1.Manager", | |
479 | "Reload"); | |
480 | if (!m) { | |
481 | log_error("Could not allocate message."); | |
482 | r = -ENOMEM; | |
483 | goto finish; | |
484 | } | |
485 | ||
486 | dbus_message_unref(reply); | |
487 | reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error); | |
488 | if (!reply) { | |
489 | log_error("Failed to issue method call: %s", bus_error_message(error)); | |
490 | r = -EIO; | |
491 | goto finish; | |
492 | } | |
493 | ||
494 | r = 0; | |
495 | ||
496 | finish: | |
497 | if (m) | |
498 | dbus_message_unref(m); | |
499 | ||
500 | if (reply) | |
501 | dbus_message_unref(reply); | |
502 | ||
503 | return r; | |
504 | } | |
505 | ||
506 | static int property_append_ntp(DBusMessageIter *i, const char *property, void *data) { | |
507 | dbus_bool_t db; | |
508 | ||
509 | assert(i); | |
510 | assert(property); | |
511 | ||
d200735e | 512 | db = tz.use_ntp > 0; |
c5f0532f LP |
513 | |
514 | if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &db)) | |
515 | return -ENOMEM; | |
516 | ||
517 | return 0; | |
518 | } | |
519 | ||
d200735e MS |
520 | static const BusProperty bus_timedate_properties[] = { |
521 | { "Timezone", bus_property_append_string, "s", offsetof(TZ, zone), true }, | |
522 | { "LocalRTC", bus_property_append_bool, "b", offsetof(TZ, local_rtc) }, | |
523 | { "NTP", property_append_ntp, "b", offsetof(TZ, use_ntp) }, | |
524 | { NULL, } | |
525 | }; | |
526 | ||
527 | static const BusBoundProperties bps[] = { | |
528 | { "org.freedesktop.timedate1", bus_timedate_properties, &tz }, | |
529 | { NULL, } | |
530 | }; | |
531 | ||
f401e48c LP |
532 | static DBusHandlerResult timedate_message_handler( |
533 | DBusConnection *connection, | |
534 | DBusMessage *message, | |
535 | void *userdata) { | |
536 | ||
f401e48c LP |
537 | DBusMessage *reply = NULL, *changed = NULL; |
538 | DBusError error; | |
539 | int r; | |
540 | ||
541 | assert(connection); | |
542 | assert(message); | |
543 | ||
544 | dbus_error_init(&error); | |
545 | ||
546 | if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetTimezone")) { | |
547 | const char *z; | |
548 | dbus_bool_t interactive; | |
549 | ||
550 | if (!dbus_message_get_args( | |
551 | message, | |
552 | &error, | |
553 | DBUS_TYPE_STRING, &z, | |
554 | DBUS_TYPE_BOOLEAN, &interactive, | |
555 | DBUS_TYPE_INVALID)) | |
556 | return bus_send_error_reply(connection, message, &error, -EINVAL); | |
557 | ||
558 | if (!valid_timezone(z)) | |
559 | return bus_send_error_reply(connection, message, NULL, -EINVAL); | |
560 | ||
d200735e | 561 | if (!streq_ptr(z, tz.zone)) { |
f401e48c LP |
562 | char *t; |
563 | ||
89f13440 | 564 | r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-timezone", interactive, NULL, &error); |
f401e48c LP |
565 | if (r < 0) |
566 | return bus_send_error_reply(connection, message, &error, r); | |
567 | ||
568 | t = strdup(z); | |
569 | if (!t) | |
570 | goto oom; | |
571 | ||
d200735e MS |
572 | free(tz.zone); |
573 | tz.zone = t; | |
f401e48c | 574 | |
2076cf88 | 575 | /* 1. Write new configuration file */ |
f401e48c LP |
576 | r = write_data_timezone(); |
577 | if (r < 0) { | |
578 | log_error("Failed to set timezone: %s", strerror(-r)); | |
579 | return bus_send_error_reply(connection, message, NULL, r); | |
580 | } | |
581 | ||
d200735e | 582 | if (tz.local_rtc) { |
2076cf88 LP |
583 | struct timespec ts; |
584 | struct tm *tm; | |
585 | ||
586 | /* 2. Teach kernel new timezone */ | |
ff4daf5a | 587 | hwclock_apply_localtime_delta(NULL); |
2076cf88 LP |
588 | |
589 | /* 3. Sync RTC from system clock, with the new delta */ | |
590 | assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0); | |
591 | assert_se(tm = localtime(&ts.tv_sec)); | |
592 | hwclock_set_time(tm); | |
593 | } | |
594 | ||
d200735e | 595 | log_info("Changed timezone to '%s'.", tz.zone); |
f401e48c LP |
596 | |
597 | changed = bus_properties_changed_new( | |
598 | "/org/freedesktop/timedate1", | |
599 | "org.freedesktop.timedate1", | |
600 | "Timezone\0"); | |
601 | if (!changed) | |
602 | goto oom; | |
603 | } | |
604 | ||
605 | } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetLocalRTC")) { | |
606 | dbus_bool_t lrtc; | |
05a4abb9 | 607 | dbus_bool_t fix_system; |
f401e48c LP |
608 | dbus_bool_t interactive; |
609 | ||
610 | if (!dbus_message_get_args( | |
611 | message, | |
612 | &error, | |
613 | DBUS_TYPE_BOOLEAN, &lrtc, | |
05a4abb9 | 614 | DBUS_TYPE_BOOLEAN, &fix_system, |
f401e48c LP |
615 | DBUS_TYPE_BOOLEAN, &interactive, |
616 | DBUS_TYPE_INVALID)) | |
617 | return bus_send_error_reply(connection, message, &error, -EINVAL); | |
618 | ||
d200735e | 619 | if (lrtc != tz.local_rtc) { |
2076cf88 LP |
620 | struct timespec ts; |
621 | ||
89f13440 | 622 | r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-local-rtc", interactive, NULL, &error); |
f401e48c LP |
623 | if (r < 0) |
624 | return bus_send_error_reply(connection, message, &error, r); | |
625 | ||
d200735e | 626 | tz.local_rtc = lrtc; |
f401e48c | 627 | |
2076cf88 | 628 | /* 1. Write new configuration file */ |
f401e48c LP |
629 | r = write_data_local_rtc(); |
630 | if (r < 0) { | |
631 | log_error("Failed to set RTC to local/UTC: %s", strerror(-r)); | |
632 | return bus_send_error_reply(connection, message, NULL, r); | |
633 | } | |
634 | ||
2076cf88 | 635 | /* 2. Teach kernel new timezone */ |
d200735e | 636 | if (tz.local_rtc) |
ff4daf5a | 637 | hwclock_apply_localtime_delta(NULL); |
2076cf88 LP |
638 | else |
639 | hwclock_reset_localtime_delta(); | |
640 | ||
641 | /* 3. Synchronize clocks */ | |
642 | assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0); | |
643 | ||
05a4abb9 | 644 | if (fix_system) { |
2076cf88 LP |
645 | struct tm tm; |
646 | ||
647 | /* Sync system clock from RTC; first, | |
648 | * initialize the timezone fields of | |
649 | * struct tm. */ | |
d200735e | 650 | if (tz.local_rtc) |
2076cf88 LP |
651 | tm = *localtime(&ts.tv_sec); |
652 | else | |
653 | tm = *gmtime(&ts.tv_sec); | |
654 | ||
655 | /* Override the main fields of | |
656 | * struct tm, but not the timezone | |
657 | * fields */ | |
658 | if (hwclock_get_time(&tm) >= 0) { | |
659 | ||
660 | /* And set the system clock | |
661 | * with this */ | |
d200735e | 662 | if (tz.local_rtc) |
2076cf88 LP |
663 | ts.tv_sec = mktime(&tm); |
664 | else | |
665 | ts.tv_sec = timegm(&tm); | |
666 | ||
667 | clock_settime(CLOCK_REALTIME, &ts); | |
668 | } | |
669 | ||
670 | } else { | |
671 | struct tm *tm; | |
672 | ||
673 | /* Sync RTC from system clock */ | |
d200735e | 674 | if (tz.local_rtc) |
2076cf88 LP |
675 | tm = localtime(&ts.tv_sec); |
676 | else | |
677 | tm = gmtime(&ts.tv_sec); | |
678 | ||
679 | hwclock_set_time(tm); | |
680 | } | |
681 | ||
d200735e | 682 | log_info("RTC configured to %s time.", tz.local_rtc ? "local" : "UTC"); |
f401e48c LP |
683 | |
684 | changed = bus_properties_changed_new( | |
685 | "/org/freedesktop/timedate1", | |
686 | "org.freedesktop.timedate1", | |
687 | "LocalRTC\0"); | |
688 | if (!changed) | |
689 | goto oom; | |
690 | } | |
691 | ||
692 | } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetTime")) { | |
693 | int64_t utc; | |
694 | dbus_bool_t relative; | |
695 | dbus_bool_t interactive; | |
696 | ||
697 | if (!dbus_message_get_args( | |
698 | message, | |
699 | &error, | |
700 | DBUS_TYPE_INT64, &utc, | |
701 | DBUS_TYPE_BOOLEAN, &relative, | |
702 | DBUS_TYPE_BOOLEAN, &interactive, | |
703 | DBUS_TYPE_INVALID)) | |
704 | return bus_send_error_reply(connection, message, &error, -EINVAL); | |
705 | ||
706 | if (!relative && utc <= 0) | |
707 | return bus_send_error_reply(connection, message, NULL, -EINVAL); | |
708 | ||
709 | if (!relative || utc != 0) { | |
710 | struct timespec ts; | |
2076cf88 | 711 | struct tm* tm; |
f401e48c | 712 | |
89f13440 | 713 | r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-time", interactive, NULL, &error); |
f401e48c LP |
714 | if (r < 0) |
715 | return bus_send_error_reply(connection, message, &error, r); | |
716 | ||
717 | if (relative) | |
718 | timespec_store(&ts, now(CLOCK_REALTIME) + utc); | |
719 | else | |
720 | timespec_store(&ts, utc); | |
721 | ||
2076cf88 | 722 | /* Set system clock */ |
f401e48c LP |
723 | if (clock_settime(CLOCK_REALTIME, &ts) < 0) { |
724 | log_error("Failed to set local time: %m"); | |
725 | return bus_send_error_reply(connection, message, NULL, -errno); | |
726 | } | |
727 | ||
2076cf88 | 728 | /* Sync down to RTC */ |
d200735e | 729 | if (tz.local_rtc) |
2076cf88 LP |
730 | tm = localtime(&ts.tv_sec); |
731 | else | |
732 | tm = gmtime(&ts.tv_sec); | |
733 | ||
734 | hwclock_set_time(tm); | |
735 | ||
f401e48c LP |
736 | log_info("Changed local time to %s", ctime(&ts.tv_sec)); |
737 | } | |
c5f0532f LP |
738 | } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetNTP")) { |
739 | dbus_bool_t ntp; | |
740 | dbus_bool_t interactive; | |
741 | ||
742 | if (!dbus_message_get_args( | |
743 | message, | |
744 | &error, | |
745 | DBUS_TYPE_BOOLEAN, &ntp, | |
746 | DBUS_TYPE_BOOLEAN, &interactive, | |
747 | DBUS_TYPE_INVALID)) | |
748 | return bus_send_error_reply(connection, message, &error, -EINVAL); | |
749 | ||
d200735e | 750 | if (ntp != !!tz.use_ntp) { |
c5f0532f | 751 | |
89f13440 | 752 | r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-ntp", interactive, NULL, &error); |
c5f0532f LP |
753 | if (r < 0) |
754 | return bus_send_error_reply(connection, message, &error, r); | |
755 | ||
d200735e | 756 | tz.use_ntp = !!ntp; |
c5f0532f LP |
757 | |
758 | r = enable_ntp(connection, &error); | |
759 | if (r < 0) | |
760 | return bus_send_error_reply(connection, message, &error, r); | |
761 | ||
762 | r = start_ntp(connection, &error); | |
763 | if (r < 0) | |
764 | return bus_send_error_reply(connection, message, &error, r); | |
765 | ||
d200735e | 766 | log_info("Set NTP to %s", tz.use_ntp ? "enabled" : "disabled"); |
c5f0532f LP |
767 | |
768 | changed = bus_properties_changed_new( | |
769 | "/org/freedesktop/timedate1", | |
770 | "org.freedesktop.timedate1", | |
771 | "NTP\0"); | |
772 | if (!changed) | |
773 | goto oom; | |
774 | } | |
f401e48c LP |
775 | |
776 | } else | |
d200735e | 777 | return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps); |
f401e48c LP |
778 | |
779 | if (!(reply = dbus_message_new_method_return(message))) | |
780 | goto oom; | |
781 | ||
782 | if (!dbus_connection_send(connection, reply, NULL)) | |
783 | goto oom; | |
784 | ||
785 | dbus_message_unref(reply); | |
786 | reply = NULL; | |
787 | ||
788 | if (changed) { | |
789 | ||
790 | if (!dbus_connection_send(connection, changed, NULL)) | |
791 | goto oom; | |
792 | ||
793 | dbus_message_unref(changed); | |
794 | } | |
795 | ||
796 | return DBUS_HANDLER_RESULT_HANDLED; | |
797 | ||
798 | oom: | |
799 | if (reply) | |
800 | dbus_message_unref(reply); | |
801 | ||
802 | if (changed) | |
803 | dbus_message_unref(changed); | |
804 | ||
805 | dbus_error_free(&error); | |
806 | ||
807 | return DBUS_HANDLER_RESULT_NEED_MEMORY; | |
808 | } | |
809 | ||
810 | static int connect_bus(DBusConnection **_bus) { | |
811 | static const DBusObjectPathVTable timedate_vtable = { | |
812 | .message_function = timedate_message_handler | |
813 | }; | |
814 | DBusError error; | |
815 | DBusConnection *bus = NULL; | |
816 | int r; | |
817 | ||
818 | assert(_bus); | |
819 | ||
820 | dbus_error_init(&error); | |
821 | ||
822 | bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error); | |
823 | if (!bus) { | |
a2e52832 | 824 | log_error("Failed to get system D-Bus connection: %s", bus_error_message(&error)); |
f401e48c LP |
825 | r = -ECONNREFUSED; |
826 | goto fail; | |
827 | } | |
828 | ||
ad740100 LP |
829 | dbus_connection_set_exit_on_disconnect(bus, FALSE); |
830 | ||
831 | if (!dbus_connection_register_object_path(bus, "/org/freedesktop/timedate1", &timedate_vtable, NULL) || | |
832 | !dbus_connection_add_filter(bus, bus_exit_idle_filter, &remain_until, NULL)) { | |
f401e48c LP |
833 | log_error("Not enough memory"); |
834 | r = -ENOMEM; | |
835 | goto fail; | |
836 | } | |
837 | ||
add10b5a LP |
838 | r = dbus_bus_request_name(bus, "org.freedesktop.timedate1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error); |
839 | if (dbus_error_is_set(&error)) { | |
840 | log_error("Failed to register name on bus: %s", bus_error_message(&error)); | |
841 | r = -EEXIST; | |
842 | goto fail; | |
843 | } | |
844 | ||
845 | if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { | |
846 | log_error("Failed to acquire name."); | |
f401e48c LP |
847 | r = -EEXIST; |
848 | goto fail; | |
849 | } | |
850 | ||
851 | if (_bus) | |
852 | *_bus = bus; | |
853 | ||
854 | return 0; | |
855 | ||
856 | fail: | |
857 | dbus_connection_close(bus); | |
858 | dbus_connection_unref(bus); | |
859 | ||
860 | dbus_error_free(&error); | |
861 | ||
862 | return r; | |
863 | } | |
864 | ||
865 | int main(int argc, char *argv[]) { | |
866 | int r; | |
867 | DBusConnection *bus = NULL; | |
ad740100 | 868 | bool exiting = false; |
f401e48c LP |
869 | |
870 | log_set_target(LOG_TARGET_AUTO); | |
871 | log_parse_environment(); | |
872 | log_open(); | |
873 | ||
4c12626c LP |
874 | umask(0022); |
875 | ||
91f9dcaf LP |
876 | if (argc == 2 && streq(argv[1], "--introspect")) { |
877 | fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE | |
878 | "<node>\n", stdout); | |
879 | fputs(timedate_interface, stdout); | |
880 | fputs("</node>\n", stdout); | |
881 | return 0; | |
882 | } | |
883 | ||
f401e48c LP |
884 | if (argc != 1) { |
885 | log_error("This program takes no arguments."); | |
886 | r = -EINVAL; | |
887 | goto finish; | |
888 | } | |
889 | ||
f401e48c LP |
890 | r = read_data(); |
891 | if (r < 0) { | |
892 | log_error("Failed to read timezone data: %s", strerror(-r)); | |
893 | goto finish; | |
894 | } | |
895 | ||
896 | r = connect_bus(&bus); | |
897 | if (r < 0) | |
898 | goto finish; | |
899 | ||
c5f0532f LP |
900 | r = read_ntp(bus); |
901 | if (r < 0) { | |
902 | log_error("Failed to determine whether NTP is enabled: %s", strerror(-r)); | |
903 | goto finish; | |
904 | } | |
905 | ||
ad740100 LP |
906 | remain_until = now(CLOCK_MONOTONIC) + DEFAULT_EXIT_USEC; |
907 | for (;;) { | |
908 | ||
909 | if (!dbus_connection_read_write_dispatch(bus, exiting ? -1 : (int) (DEFAULT_EXIT_USEC/USEC_PER_MSEC))) | |
910 | break; | |
911 | ||
912 | if (!exiting && remain_until < now(CLOCK_MONOTONIC)) { | |
913 | exiting = true; | |
914 | bus_async_unregister_and_exit(bus, "org.freedesktop.hostname1"); | |
915 | } | |
916 | } | |
f401e48c LP |
917 | |
918 | r = 0; | |
919 | ||
920 | finish: | |
921 | free_data(); | |
922 | ||
923 | if (bus) { | |
924 | dbus_connection_flush(bus); | |
925 | dbus_connection_close(bus); | |
926 | dbus_connection_unref(bus); | |
927 | } | |
928 | ||
929 | return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; | |
930 | } |