chiark / gitweb /
util: rename write_one_line_file() to write_string_file()
[elogind.git] / src / timedate / timedated.c
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 Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 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   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser 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 "systemd/sd-id128.h"
29 #include "systemd/sd-messages.h"
30 #include "util.h"
31 #include "strv.h"
32 #include "dbus-common.h"
33 #include "polkit.h"
34 #include "def.h"
35 #include "hwclock.h"
36 #include "conf-files.h"
37 #include "path-util.h"
38 #include "fileio-label.h"
39 #include "label.h"
40
41 #define NULL_ADJTIME_UTC "0.0 0 0\n0\nUTC\n"
42 #define NULL_ADJTIME_LOCAL "0.0 0 0\n0\nLOCAL\n"
43
44 #define INTERFACE                                                       \
45         " <interface name=\"org.freedesktop.timedate1\">\n"             \
46         "  <property name=\"Timezone\" type=\"s\" access=\"read\"/>\n"  \
47         "  <property name=\"LocalRTC\" type=\"b\" access=\"read\"/>\n"  \
48         "  <property name=\"CanNTP\" type=\"b\" access=\"read\"/>\n"    \
49         "  <property name=\"NTP\" type=\"b\" access=\"read\"/>\n"       \
50         "  <method name=\"SetTime\">\n"                                 \
51         "   <arg name=\"usec_utc\" type=\"x\" direction=\"in\"/>\n"     \
52         "   <arg name=\"relative\" type=\"b\" direction=\"in\"/>\n"     \
53         "   <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
54         "  </method>\n"                                                 \
55         "  <method name=\"SetTimezone\">\n"                             \
56         "   <arg name=\"timezone\" type=\"s\" direction=\"in\"/>\n"     \
57         "   <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
58         "  </method>\n"                                                 \
59         "  <method name=\"SetLocalRTC\">\n"                             \
60         "   <arg name=\"local_rtc\" type=\"b\" direction=\"in\"/>\n"    \
61         "   <arg name=\"fix_system\" type=\"b\" direction=\"in\"/>\n"   \
62         "   <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
63         "  </method>\n"                                                 \
64         "  <method name=\"SetNTP\">\n"                                  \
65         "   <arg name=\"use_ntp\" type=\"b\" direction=\"in\"/>\n"      \
66         "   <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
67         "  </method>\n"                                                 \
68         " </interface>\n"
69
70 #define INTROSPECTION                                                   \
71         DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                       \
72         "<node>\n"                                                      \
73         INTERFACE                                                       \
74         BUS_PROPERTIES_INTERFACE                                        \
75         BUS_INTROSPECTABLE_INTERFACE                                    \
76         BUS_PEER_INTERFACE                                              \
77         "</node>\n"
78
79 #define INTERFACES_LIST                         \
80         BUS_GENERIC_INTERFACES_LIST             \
81         "org.freedesktop.timedate1\0"
82
83 const char timedate_interface[] _introspect_("timedate1") = INTERFACE;
84
85 typedef struct TZ {
86         char *zone;
87         bool local_rtc;
88         int can_ntp;
89         int use_ntp;
90 } TZ;
91
92 static TZ tz = {
93         .can_ntp = -1,
94         .use_ntp = -1,
95 };
96
97 static usec_t remain_until;
98
99 static void free_data(void) {
100         free(tz.zone);
101         tz.zone = NULL;
102
103         tz.local_rtc = false;
104 }
105
106 static bool valid_timezone(const char *name) {
107         const char *p;
108         char *t;
109         bool slash = false;
110         int r;
111         struct stat st;
112
113         assert(name);
114
115         if (*name == '/' || *name == 0)
116                 return false;
117
118         for (p = name; *p; p++) {
119                 if (!(*p >= '0' && *p <= '9') &&
120                     !(*p >= 'a' && *p <= 'z') &&
121                     !(*p >= 'A' && *p <= 'Z') &&
122                     !(*p == '-' || *p == '_' || *p == '+' || *p == '/'))
123                         return false;
124
125                 if (*p == '/') {
126
127                         if (slash)
128                                 return false;
129
130                         slash = true;
131                 } else
132                         slash = false;
133         }
134
135         if (slash)
136                 return false;
137
138         t = strappend("/usr/share/zoneinfo/", name);
139         if (!t)
140                 return false;
141
142         r = stat(t, &st);
143         free(t);
144
145         if (r < 0)
146                 return false;
147
148         if (!S_ISREG(st.st_mode))
149                 return false;
150
151         return true;
152 }
153
154 static int read_data(void) {
155         int r;
156         _cleanup_free_ char *t = NULL;
157
158         free_data();
159
160         r = readlink_malloc("/etc/localtime", &t);
161         if (r < 0) {
162                 if (r == -EINVAL)
163                         log_warning("/etc/localtime should be a symbolic link to a timezone data file in /usr/share/zoneinfo/.");
164                 else
165                         log_warning("Failed to get target of /etc/localtime: %s", strerror(-r));
166         } else {
167                 const char *e;
168
169                 e = path_startswith(t, "/usr/share/zoneinfo/");
170                 if (!e)
171                         e = path_startswith(t, "../usr/share/zoneinfo/");
172
173                 if (!e)
174                         log_warning("/etc/localtime should be a symbolic link to a timezone data file in /usr/share/zoneinfo/.");
175                 else {
176                         tz.zone = strdup(e);
177                         if (!tz.zone)
178                                 return log_oom();
179
180                         goto have_timezone;
181                 }
182         }
183
184 have_timezone:
185         if (isempty(tz.zone)) {
186                 free(tz.zone);
187                 tz.zone = NULL;
188         }
189
190         tz.local_rtc = hwclock_is_localtime() > 0;
191
192         return 0;
193 }
194
195 static int write_data_timezone(void) {
196         int r = 0;
197         _cleanup_free_ char *p = NULL;
198
199         if (!tz.zone) {
200                 if (unlink("/etc/localtime") < 0 && errno != ENOENT)
201                         r = -errno;
202
203                 return r;
204         }
205
206         p = strappend("../usr/share/zoneinfo/", tz.zone);
207         if (!p)
208                 return log_oom();
209
210         r = symlink_atomic(p, "/etc/localtime");
211         if (r < 0)
212                 return r;
213
214         return 0;
215 }
216
217 static int write_data_local_rtc(void) {
218         int r;
219         char *s, *w;
220
221         r = read_full_file("/etc/adjtime", &s, NULL);
222         if (r < 0) {
223                 if (r != -ENOENT)
224                         return r;
225
226                 if (!tz.local_rtc)
227                         return 0;
228
229                 w = strdup(NULL_ADJTIME_LOCAL);
230                 if (!w)
231                         return -ENOMEM;
232         } else {
233                 char *p, *e;
234                 size_t a, b;
235
236                 p = strchr(s, '\n');
237                 if (!p) {
238                         free(s);
239                         return -EIO;
240                 }
241
242                 p = strchr(p+1, '\n');
243                 if (!p) {
244                         free(s);
245                         return -EIO;
246                 }
247
248                 p++;
249                 e = strchr(p, '\n');
250                 if (!e) {
251                         free(s);
252                         return -EIO;
253                 }
254
255                 a = p - s;
256                 b = strlen(e);
257
258                 w = new(char, a + (tz.local_rtc ? 5 : 3) + b + 1);
259                 if (!w) {
260                         free(s);
261                         return -ENOMEM;
262                 }
263
264                 *(char*) mempcpy(stpcpy(mempcpy(w, s, a), tz.local_rtc ? "LOCAL" : "UTC"), e, b) = 0;
265
266                 if (streq(w, NULL_ADJTIME_UTC)) {
267                         free(w);
268
269                         if (unlink("/etc/adjtime") < 0) {
270                                 if (errno != ENOENT)
271                                         return -errno;
272                         }
273
274                         return 0;
275                 }
276         }
277         label_init("/etc");
278         r = write_string_file_atomic_label("/etc/adjtime", w);
279         free(w);
280
281         return r;
282 }
283
284 static char** get_ntp_services(void) {
285         char **r = NULL, **files, **i;
286         int k;
287
288         k = conf_files_list(&files, ".list", NULL,
289                             "/etc/systemd/ntp-units.d",
290                             "/run/systemd/ntp-units.d",
291                             "/usr/local/lib/systemd/ntp-units.d",
292                             "/usr/lib/systemd/ntp-units.d",
293                             NULL);
294         if (k < 0)
295                 return NULL;
296
297         STRV_FOREACH(i, files) {
298                 FILE *f;
299
300                 f = fopen(*i, "re");
301                 if (!f)
302                         continue;
303
304                 for (;;) {
305                         char line[PATH_MAX], *l, **q;
306
307                         if (!fgets(line, sizeof(line), f)) {
308
309                                 if (ferror(f))
310                                         log_error("Failed to read NTP units file: %m");
311
312                                 break;
313                         }
314
315                         l = strstrip(line);
316                         if (l[0] == 0 || l[0] == '#')
317                                 continue;
318
319                         q = strv_append(r, l);
320                         if (!q) {
321                                 log_oom();
322                                 break;
323                         }
324
325                         strv_free(r);
326                         r = q;
327                 }
328
329                 fclose(f);
330         }
331
332         strv_free(files);
333
334         return strv_uniq(r);
335 }
336
337 static int read_ntp(DBusConnection *bus) {
338         DBusMessage *m = NULL, *reply = NULL;
339         DBusError error;
340         int r;
341         char **i, **l;
342
343         assert(bus);
344
345         dbus_error_init(&error);
346
347         l = get_ntp_services();
348         STRV_FOREACH(i, l) {
349                 const char *s;
350
351                 if (m)
352                         dbus_message_unref(m);
353                 m = dbus_message_new_method_call(
354                                 "org.freedesktop.systemd1",
355                                 "/org/freedesktop/systemd1",
356                                 "org.freedesktop.systemd1.Manager",
357                                 "GetUnitFileState");
358                 if (!m) {
359                         r = log_oom();
360                         goto finish;
361                 }
362
363                 if (!dbus_message_append_args(m,
364                                               DBUS_TYPE_STRING, i,
365                                               DBUS_TYPE_INVALID)) {
366                         r = log_oom();
367                         goto finish;
368                 }
369
370                 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
371                 if (!reply) {
372                         if (streq(error.name, "org.freedesktop.DBus.Error.FileNotFound")) {
373                                 /* This implementation does not exist, try next one */
374                                 dbus_error_free(&error);
375                                 continue;
376                         }
377
378                         log_error("Failed to issue method call: %s", bus_error_message(&error));
379                         r = -EIO;
380                         goto finish;
381                 }
382
383                 if (!dbus_message_get_args(reply, &error,
384                                            DBUS_TYPE_STRING, &s,
385                                            DBUS_TYPE_INVALID)) {
386                         log_error("Failed to parse reply: %s", bus_error_message(&error));
387                         r = -EIO;
388                         goto finish;
389                 }
390
391                 tz.can_ntp = 1;
392                 tz.use_ntp =
393                         streq(s, "enabled") ||
394                         streq(s, "enabled-runtime");
395                 r = 0;
396                 goto finish;
397         }
398
399         /* NTP is not installed. */
400         tz.can_ntp = 0;
401         tz.use_ntp = 0;
402         r = 0;
403
404 finish:
405         if (m)
406                 dbus_message_unref(m);
407
408         if (reply)
409                 dbus_message_unref(reply);
410
411         strv_free(l);
412
413         dbus_error_free(&error);
414
415         return r;
416 }
417
418 static int start_ntp(DBusConnection *bus, DBusError *error) {
419         DBusMessage *m = NULL, *reply = NULL;
420         const char *mode = "replace";
421         char **i, **l;
422         int r;
423
424         assert(bus);
425         assert(error);
426
427         l = get_ntp_services();
428         STRV_FOREACH(i, l) {
429                 if (m)
430                         dbus_message_unref(m);
431                 m = dbus_message_new_method_call(
432                                 "org.freedesktop.systemd1",
433                                 "/org/freedesktop/systemd1",
434                                 "org.freedesktop.systemd1.Manager",
435                                 tz.use_ntp ? "StartUnit" : "StopUnit");
436                 if (!m) {
437                         log_error("Could not allocate message.");
438                         r = -ENOMEM;
439                         goto finish;
440                 }
441
442                 if (!dbus_message_append_args(m,
443                                               DBUS_TYPE_STRING, i,
444                                               DBUS_TYPE_STRING, &mode,
445                                               DBUS_TYPE_INVALID)) {
446                         log_error("Could not append arguments to message.");
447                         r = -ENOMEM;
448                         goto finish;
449                 }
450
451                 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
452                 if (!reply) {
453                         if (streq(error->name, "org.freedesktop.DBus.Error.FileNotFound") ||
454                             streq(error->name, "org.freedesktop.systemd1.LoadFailed") ||
455                             streq(error->name, "org.freedesktop.systemd1.NoSuchUnit")) {
456                                 /* This implementation does not exist, try next one */
457                                 dbus_error_free(error);
458                                 continue;
459                         }
460
461                         log_error("Failed to issue method call: %s", bus_error_message(error));
462                         r = -EIO;
463                         goto finish;
464                 }
465
466                 r = 0;
467                 goto finish;
468         }
469
470         /* No implementaiton available... */
471         r = -ENOENT;
472
473 finish:
474         if (m)
475                 dbus_message_unref(m);
476
477         if (reply)
478                 dbus_message_unref(reply);
479
480         strv_free(l);
481
482         return r;
483 }
484
485 static int enable_ntp(DBusConnection *bus, DBusError *error) {
486         DBusMessage *m = NULL, *reply = NULL;
487         int r;
488         DBusMessageIter iter;
489         dbus_bool_t f = FALSE, t = TRUE;
490         char **i, **l;
491
492         assert(bus);
493         assert(error);
494
495         l = get_ntp_services();
496         STRV_FOREACH(i, l) {
497                 char* k[2];
498
499                 if (m)
500                         dbus_message_unref(m);
501                 m = dbus_message_new_method_call(
502                                 "org.freedesktop.systemd1",
503                                 "/org/freedesktop/systemd1",
504                                 "org.freedesktop.systemd1.Manager",
505                                 tz.use_ntp ? "EnableUnitFiles" : "DisableUnitFiles");
506                 if (!m) {
507                         log_error("Could not allocate message.");
508                         r = -ENOMEM;
509                         goto finish;
510                 }
511
512                 dbus_message_iter_init_append(m, &iter);
513
514                 k[0] = *i;
515                 k[1] = NULL;
516
517                 r = bus_append_strv_iter(&iter, k);
518                 if (r < 0) {
519                         log_error("Failed to append unit files.");
520                         goto finish;
521                 }
522
523                 /* send runtime bool */
524                 if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &f)) {
525                         log_error("Failed to append runtime boolean.");
526                         r = -ENOMEM;
527                         goto finish;
528                 }
529
530                 if (tz.use_ntp) {
531                         /* send force bool */
532                         if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &t)) {
533                                 log_error("Failed to append force boolean.");
534                                 r = -ENOMEM;
535                                 goto finish;
536                         }
537                 }
538
539                 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
540                 if (!reply) {
541                         if (streq(error->name, "org.freedesktop.DBus.Error.FileNotFound")) {
542                                 /* This implementation does not exist, try next one */
543                                 dbus_error_free(error);
544                                 continue;
545                         }
546
547                         log_error("Failed to issue method call: %s", bus_error_message(error));
548                         r = -EIO;
549                         goto finish;
550                 }
551
552                 dbus_message_unref(m);
553                 m = dbus_message_new_method_call(
554                                 "org.freedesktop.systemd1",
555                                 "/org/freedesktop/systemd1",
556                                 "org.freedesktop.systemd1.Manager",
557                                 "Reload");
558                 if (!m) {
559                         log_error("Could not allocate message.");
560                         r = -ENOMEM;
561                         goto finish;
562                 }
563
564                 dbus_message_unref(reply);
565                 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, error);
566                 if (!reply) {
567                         log_error("Failed to issue method call: %s", bus_error_message(error));
568                         r = -EIO;
569                         goto finish;
570                 }
571
572                 r = 0;
573                 goto finish;
574         }
575
576         r = -ENOENT;
577
578 finish:
579         if (m)
580                 dbus_message_unref(m);
581
582         if (reply)
583                 dbus_message_unref(reply);
584
585         strv_free(l);
586
587         return r;
588 }
589
590 static int property_append_can_ntp(DBusMessageIter *i, const char *property, void *data) {
591         dbus_bool_t db;
592
593         assert(i);
594         assert(property);
595
596         db = tz.can_ntp > 0;
597
598         if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &db))
599                 return -ENOMEM;
600
601         return 0;
602 }
603
604 static int property_append_ntp(DBusMessageIter *i, const char *property, void *data) {
605         dbus_bool_t db;
606
607         assert(i);
608         assert(property);
609
610         db = tz.use_ntp > 0;
611
612         if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &db))
613                 return -ENOMEM;
614
615         return 0;
616 }
617
618 static const BusProperty bus_timedate_properties[] = {
619         { "Timezone", bus_property_append_string, "s", offsetof(TZ, zone),     true },
620         { "LocalRTC", bus_property_append_bool,   "b", offsetof(TZ, local_rtc) },
621         { "CanNTP",   property_append_can_ntp,    "b", offsetof(TZ, can_ntp)   },
622         { "NTP",      property_append_ntp,        "b", offsetof(TZ, use_ntp)   },
623         { NULL, }
624 };
625
626 static const BusBoundProperties bps[] = {
627         { "org.freedesktop.timedate1", bus_timedate_properties, &tz },
628         { NULL, }
629 };
630
631 static DBusHandlerResult timedate_message_handler(
632                 DBusConnection *connection,
633                 DBusMessage *message,
634                 void *userdata) {
635
636         DBusMessage *reply = NULL, *changed = NULL;
637         DBusError error;
638         int r;
639
640         assert(connection);
641         assert(message);
642
643         dbus_error_init(&error);
644
645         if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetTimezone")) {
646                 const char *z;
647                 dbus_bool_t interactive;
648
649                 if (!dbus_message_get_args(
650                                     message,
651                                     &error,
652                                     DBUS_TYPE_STRING, &z,
653                                     DBUS_TYPE_BOOLEAN, &interactive,
654                                     DBUS_TYPE_INVALID))
655                         return bus_send_error_reply(connection, message, &error, -EINVAL);
656
657                 if (!valid_timezone(z))
658                         return bus_send_error_reply(connection, message, NULL, -EINVAL);
659
660                 if (!streq_ptr(z, tz.zone)) {
661                         char *t;
662
663                         r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-timezone", interactive, NULL, &error);
664                         if (r < 0)
665                                 return bus_send_error_reply(connection, message, &error, r);
666
667                         t = strdup(z);
668                         if (!t)
669                                 goto oom;
670
671                         free(tz.zone);
672                         tz.zone = t;
673
674                         /* 1. Write new configuration file */
675                         r = write_data_timezone();
676                         if (r < 0) {
677                                 log_error("Failed to set timezone: %s", strerror(-r));
678                                 return bus_send_error_reply(connection, message, NULL, r);
679                         }
680
681                         /* 2. Tell the kernel our time zone */
682                         hwclock_set_timezone(NULL);
683
684                         if (tz.local_rtc) {
685                                 struct timespec ts;
686                                 struct tm *tm;
687
688                                 /* 3. Sync RTC from system clock, with the new delta */
689                                 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
690                                 assert_se(tm = localtime(&ts.tv_sec));
691                                 hwclock_set_time(tm);
692                         }
693
694                         log_struct(LOG_INFO,
695                                    MESSAGE_ID(SD_MESSAGE_TIMEZONE_CHANGE),
696                                    "TIMEZONE=%s", tz.zone,
697                                    "MESSAGE=Changed timezone to '%s'.", tz.zone,
698                                    NULL);
699
700                         changed = bus_properties_changed_new(
701                                         "/org/freedesktop/timedate1",
702                                         "org.freedesktop.timedate1",
703                                         "Timezone\0");
704                         if (!changed)
705                                 goto oom;
706                 }
707
708         } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetLocalRTC")) {
709                 dbus_bool_t lrtc;
710                 dbus_bool_t fix_system;
711                 dbus_bool_t interactive;
712
713                 if (!dbus_message_get_args(
714                                     message,
715                                     &error,
716                                     DBUS_TYPE_BOOLEAN, &lrtc,
717                                     DBUS_TYPE_BOOLEAN, &fix_system,
718                                     DBUS_TYPE_BOOLEAN, &interactive,
719                                     DBUS_TYPE_INVALID))
720                         return bus_send_error_reply(connection, message, &error, -EINVAL);
721
722                 if (lrtc != tz.local_rtc) {
723                         struct timespec ts;
724
725                         r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-local-rtc", interactive, NULL, &error);
726                         if (r < 0)
727                                 return bus_send_error_reply(connection, message, &error, r);
728
729                         tz.local_rtc = lrtc;
730
731                         /* 1. Write new configuration file */
732                         r = write_data_local_rtc();
733                         if (r < 0) {
734                                 log_error("Failed to set RTC to local/UTC: %s", strerror(-r));
735                                 return bus_send_error_reply(connection, message, NULL, r);
736                         }
737
738                         /* 2. Tell the kernel our time zone */
739                         hwclock_set_timezone(NULL);
740
741                         /* 3. Synchronize clocks */
742                         assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
743
744                         if (fix_system) {
745                                 struct tm tm;
746
747                                 /* Sync system clock from RTC; first,
748                                  * initialize the timezone fields of
749                                  * struct tm. */
750                                 if (tz.local_rtc)
751                                         tm = *localtime(&ts.tv_sec);
752                                 else
753                                         tm = *gmtime(&ts.tv_sec);
754
755                                 /* Override the main fields of
756                                  * struct tm, but not the timezone
757                                  * fields */
758                                 if (hwclock_get_time(&tm) >= 0) {
759
760                                         /* And set the system clock
761                                          * with this */
762                                         if (tz.local_rtc)
763                                                 ts.tv_sec = mktime(&tm);
764                                         else
765                                                 ts.tv_sec = timegm(&tm);
766
767                                         clock_settime(CLOCK_REALTIME, &ts);
768                                 }
769
770                         } else {
771                                 struct tm *tm;
772
773                                 /* Sync RTC from system clock */
774                                 if (tz.local_rtc)
775                                         tm = localtime(&ts.tv_sec);
776                                 else
777                                         tm = gmtime(&ts.tv_sec);
778
779                                 hwclock_set_time(tm);
780                         }
781
782                         log_info("RTC configured to %s time.", tz.local_rtc ? "local" : "UTC");
783
784                         changed = bus_properties_changed_new(
785                                         "/org/freedesktop/timedate1",
786                                         "org.freedesktop.timedate1",
787                                         "LocalRTC\0");
788                         if (!changed)
789                                 goto oom;
790                 }
791
792         } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetTime")) {
793                 int64_t utc;
794                 dbus_bool_t relative;
795                 dbus_bool_t interactive;
796
797                 if (!dbus_message_get_args(
798                                     message,
799                                     &error,
800                                     DBUS_TYPE_INT64, &utc,
801                                     DBUS_TYPE_BOOLEAN, &relative,
802                                     DBUS_TYPE_BOOLEAN, &interactive,
803                                     DBUS_TYPE_INVALID))
804                         return bus_send_error_reply(connection, message, &error, -EINVAL);
805
806                 if (!relative && utc <= 0)
807                         return bus_send_error_reply(connection, message, NULL, -EINVAL);
808
809                 if (!relative || utc != 0) {
810                         struct timespec ts;
811                         struct tm* tm;
812
813                         if (relative) {
814                                 usec_t n, x;
815
816                                 n = now(CLOCK_REALTIME);
817                                 x = n + utc;
818
819                                 if ((utc > 0 && x < n) ||
820                                     (utc < 0 && x > n))
821                                         return bus_send_error_reply(connection, message, NULL, -EOVERFLOW);
822
823                                 timespec_store(&ts, x);
824                         } else
825                                 timespec_store(&ts, (usec_t) utc);
826
827                         r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-time", interactive, NULL, &error);
828                         if (r < 0)
829                                 return bus_send_error_reply(connection, message, &error, r);
830
831                         /* Set system clock */
832                         if (clock_settime(CLOCK_REALTIME, &ts) < 0) {
833                                 log_error("Failed to set local time: %m");
834                                 return bus_send_error_reply(connection, message, NULL, -errno);
835                         }
836
837                         /* Sync down to RTC */
838                         if (tz.local_rtc)
839                                 tm = localtime(&ts.tv_sec);
840                         else
841                                 tm = gmtime(&ts.tv_sec);
842
843                         hwclock_set_time(tm);
844
845                         log_struct(LOG_INFO,
846                                    MESSAGE_ID(SD_MESSAGE_TIME_CHANGE),
847                                    "REALTIME=%llu", (unsigned long long) timespec_load(&ts),
848                                    "MESSAGE=Changed local time to %s", ctime(&ts.tv_sec),
849                                    NULL);
850                 }
851         } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetNTP")) {
852                 dbus_bool_t ntp;
853                 dbus_bool_t interactive;
854
855                 if (!dbus_message_get_args(
856                                     message,
857                                     &error,
858                                     DBUS_TYPE_BOOLEAN, &ntp,
859                                     DBUS_TYPE_BOOLEAN, &interactive,
860                                     DBUS_TYPE_INVALID))
861                         return bus_send_error_reply(connection, message, &error, -EINVAL);
862
863                 if (ntp != !!tz.use_ntp) {
864
865                         r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-ntp", interactive, NULL, &error);
866                         if (r < 0)
867                                 return bus_send_error_reply(connection, message, &error, r);
868
869                         tz.use_ntp = !!ntp;
870
871                         r = enable_ntp(connection, &error);
872                         if (r < 0)
873                                 return bus_send_error_reply(connection, message, &error, r);
874
875                         r = start_ntp(connection, &error);
876                         if (r < 0)
877                                 return bus_send_error_reply(connection, message, &error, r);
878
879                         log_info("Set NTP to %s", tz.use_ntp ? "enabled" : "disabled");
880
881                         changed = bus_properties_changed_new(
882                                         "/org/freedesktop/timedate1",
883                                         "org.freedesktop.timedate1",
884                                         "NTP\0");
885                         if (!changed)
886                                 goto oom;
887                 }
888
889         } else
890                 return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps);
891
892         if (!(reply = dbus_message_new_method_return(message)))
893                 goto oom;
894
895         if (!bus_maybe_send_reply(connection, message, reply))
896                 goto oom;
897
898         dbus_message_unref(reply);
899         reply = NULL;
900
901         if (changed) {
902
903                 if (!dbus_connection_send(connection, changed, NULL))
904                         goto oom;
905
906                 dbus_message_unref(changed);
907         }
908
909         return DBUS_HANDLER_RESULT_HANDLED;
910
911 oom:
912         if (reply)
913                 dbus_message_unref(reply);
914
915         if (changed)
916                 dbus_message_unref(changed);
917
918         dbus_error_free(&error);
919
920         return DBUS_HANDLER_RESULT_NEED_MEMORY;
921 }
922
923 static int connect_bus(DBusConnection **_bus) {
924         static const DBusObjectPathVTable timedate_vtable = {
925                 .message_function = timedate_message_handler
926         };
927         DBusError error;
928         DBusConnection *bus = NULL;
929         int r;
930
931         assert(_bus);
932
933         dbus_error_init(&error);
934
935         bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
936         if (!bus) {
937                 log_error("Failed to get system D-Bus connection: %s", bus_error_message(&error));
938                 r = -ECONNREFUSED;
939                 goto fail2;
940         }
941
942         dbus_connection_set_exit_on_disconnect(bus, FALSE);
943
944         if (!dbus_connection_register_object_path(bus, "/org/freedesktop/timedate1", &timedate_vtable, NULL) ||
945             !dbus_connection_add_filter(bus, bus_exit_idle_filter, &remain_until, NULL)) {
946                 r = log_oom();
947                 goto fail;
948         }
949
950         r = dbus_bus_request_name(bus, "org.freedesktop.timedate1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error);
951         if (dbus_error_is_set(&error)) {
952                 log_error("Failed to register name on bus: %s", bus_error_message(&error));
953                 r = -EEXIST;
954                 goto fail;
955         }
956
957         if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER)  {
958                 log_error("Failed to acquire name.");
959                 r = -EEXIST;
960                 goto fail;
961         }
962
963         if (_bus)
964                 *_bus = bus;
965
966         return 0;
967
968 fail:
969         dbus_connection_close(bus);
970         dbus_connection_unref(bus);
971 fail2:
972         dbus_error_free(&error);
973
974         return r;
975 }
976
977 int main(int argc, char *argv[]) {
978         int r;
979         DBusConnection *bus = NULL;
980         bool exiting = false;
981
982         log_set_target(LOG_TARGET_AUTO);
983         log_parse_environment();
984         log_open();
985
986         umask(0022);
987
988         if (argc == 2 && streq(argv[1], "--introspect")) {
989                 fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
990                       "<node>\n", stdout);
991                 fputs(timedate_interface, stdout);
992                 fputs("</node>\n", stdout);
993                 return 0;
994         }
995
996         if (argc != 1) {
997                 log_error("This program takes no arguments.");
998                 r = -EINVAL;
999                 goto finish;
1000         }
1001
1002         r = read_data();
1003         if (r < 0) {
1004                 log_error("Failed to read timezone data: %s", strerror(-r));
1005                 goto finish;
1006         }
1007
1008         r = connect_bus(&bus);
1009         if (r < 0)
1010                 goto finish;
1011
1012         r = read_ntp(bus);
1013         if (r < 0) {
1014                 log_error("Failed to determine whether NTP is enabled: %s", strerror(-r));
1015                 goto finish;
1016         }
1017
1018         remain_until = now(CLOCK_MONOTONIC) + DEFAULT_EXIT_USEC;
1019         for (;;) {
1020
1021                 if (!dbus_connection_read_write_dispatch(bus, exiting ? -1 : (int) (DEFAULT_EXIT_USEC/USEC_PER_MSEC)))
1022                         break;
1023
1024                 if (!exiting && remain_until < now(CLOCK_MONOTONIC)) {
1025                         exiting = true;
1026                         bus_async_unregister_and_exit(bus, "org.freedesktop.timedated1");
1027                 }
1028         }
1029
1030         r = 0;
1031
1032 finish:
1033         free_data();
1034
1035         if (bus) {
1036                 dbus_connection_flush(bus);
1037                 dbus_connection_close(bus);
1038                 dbus_connection_unref(bus);
1039         }
1040
1041         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
1042 }