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