chiark / gitweb /
build-sys: Add i18n support through intltool
[elogind.git] / src / 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 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
36 #define INTERFACE                                                       \
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"  \
40         "  <method name=\"SetTime\">\n"                                 \
41         "   <arg name=\"usec_utc\" type=\"x\" direction=\"in\"/>\n"     \
42         "   <arg name=\"relative\" type=\"b\" direction=\"in\"/>\n"     \
43         "   <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
44         "  </method>\n"                                                 \
45         "  <method name=\"SetTimezone\">\n"                             \
46         "   <arg name=\"timezone\" type=\"s\" direction=\"in\"/>\n"     \
47         "   <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
48         "  </method>\n"                                                 \
49         "  <method name=\"SetLocalRTC\">\n"                             \
50         "   <arg name=\"local_rtc\" type=\"b\" direction=\"in\"/>\n"    \
51         "   <arg name=\"fix_system\" type=\"b\" direction=\"in\"/>\n"   \
52         "   <arg name=\"user_interaction\" type=\"b\" direction=\"in\"/>\n" \
53         "  </method>\n"                                                 \
54         " </interface>\n"
55
56 #define INTROSPECTION                                                   \
57         DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                       \
58         "<node>\n"                                                      \
59         INTERFACE                                                       \
60         BUS_PROPERTIES_INTERFACE                                        \
61         BUS_INTROSPECTABLE_INTERFACE                                    \
62         BUS_PEER_INTERFACE                                              \
63         "</node>\n"
64
65 #define INTERFACES_LIST                         \
66         BUS_GENERIC_INTERFACES_LIST             \
67         "org.freedesktop.locale1\0"
68
69 const char timedate_interface[] _introspect_("timedate1") = INTERFACE;
70
71 static char *zone = NULL;
72 static bool local_rtc = false;
73
74 static void free_data(void) {
75         free(zone);
76         zone = NULL;
77
78         local_rtc = false;
79 }
80
81 static bool valid_timezone(const char *name) {
82         const char *p;
83         char *t;
84         bool slash = false;
85         int r;
86         struct stat st;
87
88         assert(name);
89
90         if (*name == '/' || *name == 0)
91                 return false;
92
93         for (p = name; *p; p++) {
94                 if (!(*p >= '0' && *p <= '9') &&
95                     !(*p >= 'a' && *p <= 'z') &&
96                     !(*p >= 'A' && *p <= 'Z') &&
97                     !(*p == '-' || *p == '_' || *p == '+' || *p == '/'))
98                         return false;
99
100                 if (*p == '/') {
101
102                         if (slash)
103                                 return false;
104
105                         slash = true;
106                 } else
107                         slash = false;
108         }
109
110         if (slash)
111                 return false;
112
113         t = strappend("/usr/share/zoneinfo/", name);
114         if (!t)
115                 return false;
116
117         r = stat(t, &st);
118         free(t);
119
120         if (r < 0)
121                 return false;
122
123         if (!S_ISREG(st.st_mode))
124                 return false;
125
126         return true;
127 }
128
129 static void verify_timezone(void) {
130         char *p, *a = NULL, *b = NULL;
131         size_t l, q;
132         int j, k;
133
134         if (!zone)
135                 return;
136
137         p = strappend("/usr/share/zoneinfo/", zone);
138         if (!p) {
139                 log_error("Out of memory");
140                 return;
141         }
142
143         j = read_full_file("/etc/localtime", &a, &l);
144         k = read_full_file(p, &b, &q);
145
146         free(p);
147
148         if (j < 0 || k < 0 || l != q || memcmp(a, b, l)) {
149                 log_warning("/etc/localtime and /etc/timezone out of sync.");
150                 free(zone);
151                 zone = NULL;
152         }
153
154         free(a);
155         free(b);
156 }
157
158 static int read_data(void) {
159         int r;
160
161         free_data();
162
163         r = read_one_line_file("/etc/timezone", &zone);
164         if (r < 0 && r != -ENOENT)
165                 return r;
166
167         verify_timezone();
168
169         local_rtc = hwclock_is_localtime() > 0;
170
171         return 0;
172 }
173
174 static int write_data_timezone(void) {
175         int r = 0;
176         char *p;
177
178         if (!zone) {
179                 if (unlink("/etc/timezone") < 0 && errno != ENOENT)
180                         r = -errno;
181
182                 if (unlink("/etc/localtime") < 0 && errno != ENOENT)
183                         r = -errno;
184
185                 return r;
186         }
187
188         p = strappend("/usr/share/zoneinfo/", zone);
189         if (!p) {
190                 log_error("Out of memory");
191                 return -ENOMEM;
192         }
193
194         r = symlink_or_copy_atomic(p, "/etc/localtime");
195         free(p);
196
197         if (r < 0)
198                 return r;
199
200         r = write_one_line_file_atomic("/etc/timezone", zone);
201         if (r < 0)
202                 return r;
203
204         return 0;
205 }
206
207 static int write_data_local_rtc(void) {
208         int r;
209         char *s, *w;
210
211         r = read_full_file("/etc/adjtime", &s, NULL);
212         if (r < 0) {
213                 if (r != -ENOENT)
214                         return r;
215
216                 if (!local_rtc)
217                         return 0;
218
219                 w = strdup(NULL_ADJTIME_LOCAL);
220                 if (!w)
221                         return -ENOMEM;
222         } else {
223                 char *p, *e;
224                 size_t a, b;
225
226                 p = strchr(s, '\n');
227                 if (!p) {
228                         free(s);
229                         return -EIO;
230                 }
231
232                 p = strchr(p+1, '\n');
233                 if (!p) {
234                         free(s);
235                         return -EIO;
236                 }
237
238                 p++;
239                 e = strchr(p, '\n');
240                 if (!p) {
241                         free(s);
242                         return -EIO;
243                 }
244
245                 a = p - s;
246                 b = strlen(e);
247
248                 w = new(char, a + (local_rtc ? 5 : 3) + b + 1);
249                 if (!w) {
250                         free(s);
251                         return -ENOMEM;
252                 }
253
254                 *(char*) mempcpy(stpcpy(mempcpy(w, s, a), local_rtc ? "LOCAL" : "UTC"), e, b) = 0;
255
256                 if (streq(w, NULL_ADJTIME_UTC)) {
257                         free(w);
258
259                         if (unlink("/etc/adjtime") < 0) {
260                                 if (errno != ENOENT)
261                                         return -errno;
262                         }
263
264                         return 0;
265                 }
266         }
267
268         r = write_one_line_file_atomic("/etc/adjtime", w);
269         free(w);
270
271         return r;
272 }
273
274 static DBusHandlerResult timedate_message_handler(
275                 DBusConnection *connection,
276                 DBusMessage *message,
277                 void *userdata) {
278
279         const BusProperty properties[] = {
280                 { "org.freedesktop.timedate1", "Timezone", bus_property_append_string, "s", zone       },
281                 { "org.freedesktop.timedate1", "LocalRTC", bus_property_append_bool,   "b", &local_rtc },
282                 { NULL, NULL, NULL, NULL, NULL }
283         };
284
285         DBusMessage *reply = NULL, *changed = NULL;
286         DBusError error;
287         int r;
288
289         assert(connection);
290         assert(message);
291
292         dbus_error_init(&error);
293
294         if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetTimezone")) {
295                 const char *z;
296                 dbus_bool_t interactive;
297
298                 if (!dbus_message_get_args(
299                                     message,
300                                     &error,
301                                     DBUS_TYPE_STRING, &z,
302                                     DBUS_TYPE_BOOLEAN, &interactive,
303                                     DBUS_TYPE_INVALID))
304                         return bus_send_error_reply(connection, message, &error, -EINVAL);
305
306                 if (!valid_timezone(z))
307                         return bus_send_error_reply(connection, message, NULL, -EINVAL);
308
309                 if (!streq_ptr(z, zone)) {
310                         char *t;
311
312                         r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-timezone", interactive, &error);
313                         if (r < 0)
314                                 return bus_send_error_reply(connection, message, &error, r);
315
316                         t = strdup(z);
317                         if (!t)
318                                 goto oom;
319
320                         free(zone);
321                         zone = t;
322
323                         /* 1. Write new configuration file */
324                         r = write_data_timezone();
325                         if (r < 0) {
326                                 log_error("Failed to set timezone: %s", strerror(-r));
327                                 return bus_send_error_reply(connection, message, NULL, r);
328                         }
329
330                         if (local_rtc) {
331                                 struct timespec ts;
332                                 struct tm *tm;
333
334                                 /* 2. Teach kernel new timezone */
335                                 hwclock_apply_localtime_delta();
336
337                                 /* 3. Sync RTC from system clock, with the new delta */
338                                 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
339                                 assert_se(tm = localtime(&ts.tv_sec));
340                                 hwclock_set_time(tm);
341                         }
342
343                         log_info("Changed timezone to '%s'.", zone);
344
345                         changed = bus_properties_changed_new(
346                                         "/org/freedesktop/timedate1",
347                                         "org.freedesktop.timedate1",
348                                         "Timezone\0");
349                         if (!changed)
350                                 goto oom;
351                 }
352
353         } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetLocalRTC")) {
354                 dbus_bool_t lrtc;
355                 dbus_bool_t fix_system;
356                 dbus_bool_t interactive;
357
358                 if (!dbus_message_get_args(
359                                     message,
360                                     &error,
361                                     DBUS_TYPE_BOOLEAN, &lrtc,
362                                     DBUS_TYPE_BOOLEAN, &fix_system,
363                                     DBUS_TYPE_BOOLEAN, &interactive,
364                                     DBUS_TYPE_INVALID))
365                         return bus_send_error_reply(connection, message, &error, -EINVAL);
366
367                 if (lrtc != local_rtc) {
368                         struct timespec ts;
369
370                         r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-local-rtc", interactive, &error);
371                         if (r < 0)
372                                 return bus_send_error_reply(connection, message, &error, r);
373
374                         local_rtc = lrtc;
375
376                         /* 1. Write new configuration file */
377                         r = write_data_local_rtc();
378                         if (r < 0) {
379                                 log_error("Failed to set RTC to local/UTC: %s", strerror(-r));
380                                 return bus_send_error_reply(connection, message, NULL, r);
381                         }
382
383                         /* 2. Teach kernel new timezone */
384                         if (local_rtc)
385                                 hwclock_apply_localtime_delta();
386                         else
387                                 hwclock_reset_localtime_delta();
388
389                         /* 3. Synchronize clocks */
390                         assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
391
392                         if (fix_system) {
393                                 struct tm tm;
394
395                                 /* Sync system clock from RTC; first,
396                                  * initialize the timezone fields of
397                                  * struct tm. */
398                                 if (local_rtc)
399                                         tm = *localtime(&ts.tv_sec);
400                                 else
401                                         tm = *gmtime(&ts.tv_sec);
402
403                                 /* Override the main fields of
404                                  * struct tm, but not the timezone
405                                  * fields */
406                                 if (hwclock_get_time(&tm) >= 0) {
407
408                                         /* And set the system clock
409                                          * with this */
410                                         if (local_rtc)
411                                                 ts.tv_sec = mktime(&tm);
412                                         else
413                                                 ts.tv_sec = timegm(&tm);
414
415                                         clock_settime(CLOCK_REALTIME, &ts);
416                                 }
417
418                         } else {
419                                 struct tm *tm;
420
421                                 /* Sync RTC from system clock */
422                                 if (local_rtc)
423                                         tm = localtime(&ts.tv_sec);
424                                 else
425                                         tm = gmtime(&ts.tv_sec);
426
427                                 hwclock_set_time(tm);
428                         }
429
430                         log_error("RTC configured to %s time.", local_rtc ? "local" : "UTC");
431
432                         changed = bus_properties_changed_new(
433                                         "/org/freedesktop/timedate1",
434                                         "org.freedesktop.timedate1",
435                                         "LocalRTC\0");
436                         if (!changed)
437                                 goto oom;
438                 }
439
440         } else if (dbus_message_is_method_call(message, "org.freedesktop.timedate1", "SetTime")) {
441                 int64_t utc;
442                 dbus_bool_t relative;
443                 dbus_bool_t interactive;
444
445                 if (!dbus_message_get_args(
446                                     message,
447                                     &error,
448                                     DBUS_TYPE_INT64, &utc,
449                                     DBUS_TYPE_BOOLEAN, &relative,
450                                     DBUS_TYPE_BOOLEAN, &interactive,
451                                     DBUS_TYPE_INVALID))
452                         return bus_send_error_reply(connection, message, &error, -EINVAL);
453
454                 if (!relative && utc <= 0)
455                         return bus_send_error_reply(connection, message, NULL, -EINVAL);
456
457                 if (!relative || utc != 0) {
458                         struct timespec ts;
459                         struct tm* tm;
460
461                         r = verify_polkit(connection, message, "org.freedesktop.timedate1.set-time", interactive, &error);
462                         if (r < 0)
463                                 return bus_send_error_reply(connection, message, &error, r);
464
465                         if (relative)
466                                 timespec_store(&ts, now(CLOCK_REALTIME) + utc);
467                         else
468                                 timespec_store(&ts, utc);
469
470                         /* Set system clock */
471                         if (clock_settime(CLOCK_REALTIME, &ts) < 0) {
472                                 log_error("Failed to set local time: %m");
473                                 return bus_send_error_reply(connection, message, NULL, -errno);
474                         }
475
476                         /* Sync down to RTC */
477                         if (local_rtc)
478                                 tm = localtime(&ts.tv_sec);
479                         else
480                                 tm = gmtime(&ts.tv_sec);
481
482                         hwclock_set_time(tm);
483
484                         log_info("Changed local time to %s", ctime(&ts.tv_sec));
485                 }
486
487         } else
488                 return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, properties);
489
490         if (!(reply = dbus_message_new_method_return(message)))
491                 goto oom;
492
493         if (!dbus_connection_send(connection, reply, NULL))
494                 goto oom;
495
496         dbus_message_unref(reply);
497         reply = NULL;
498
499         if (changed) {
500
501                 if (!dbus_connection_send(connection, changed, NULL))
502                         goto oom;
503
504                 dbus_message_unref(changed);
505         }
506
507         return DBUS_HANDLER_RESULT_HANDLED;
508
509 oom:
510         if (reply)
511                 dbus_message_unref(reply);
512
513         if (changed)
514                 dbus_message_unref(changed);
515
516         dbus_error_free(&error);
517
518         return DBUS_HANDLER_RESULT_NEED_MEMORY;
519 }
520
521 static int connect_bus(DBusConnection **_bus) {
522         static const DBusObjectPathVTable timedate_vtable = {
523                 .message_function = timedate_message_handler
524         };
525         DBusError error;
526         DBusConnection *bus = NULL;
527         int r;
528
529         assert(_bus);
530
531         dbus_error_init(&error);
532
533         bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
534         if (!bus) {
535                 log_error("Failed to get system D-Bus connection: %s", bus_error_message(&error));
536                 r = -ECONNREFUSED;
537                 goto fail;
538         }
539
540         if (!dbus_connection_register_object_path(bus, "/org/freedesktop/timedate1", &timedate_vtable, NULL)) {
541                 log_error("Not enough memory");
542                 r = -ENOMEM;
543                 goto fail;
544         }
545
546         r = dbus_bus_request_name(bus, "org.freedesktop.timedate1", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error);
547         if (dbus_error_is_set(&error)) {
548                 log_error("Failed to register name on bus: %s", bus_error_message(&error));
549                 r = -EEXIST;
550                 goto fail;
551         }
552
553         if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER)  {
554                 log_error("Failed to acquire name.");
555                 r = -EEXIST;
556                 goto fail;
557         }
558
559         if (_bus)
560                 *_bus = bus;
561
562         return 0;
563
564 fail:
565         dbus_connection_close(bus);
566         dbus_connection_unref(bus);
567
568         dbus_error_free(&error);
569
570         return r;
571 }
572
573 int main(int argc, char *argv[]) {
574         int r;
575         DBusConnection *bus = NULL;
576
577         log_set_target(LOG_TARGET_AUTO);
578         log_parse_environment();
579         log_open();
580
581         if (argc == 2 && streq(argv[1], "--introspect")) {
582                 fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
583                       "<node>\n", stdout);
584                 fputs(timedate_interface, stdout);
585                 fputs("</node>\n", stdout);
586                 return 0;
587         }
588
589         if (argc != 1) {
590                 log_error("This program takes no arguments.");
591                 r = -EINVAL;
592                 goto finish;
593         }
594
595         umask(0022);
596
597         r = read_data();
598         if (r < 0) {
599                 log_error("Failed to read timezone data: %s", strerror(-r));
600                 goto finish;
601         }
602
603         r = connect_bus(&bus);
604         if (r < 0)
605                 goto finish;
606
607         while (dbus_connection_read_write_dispatch(bus, -1))
608                 ;
609
610         r = 0;
611
612 finish:
613         free_data();
614
615         if (bus) {
616                 dbus_connection_flush(bus);
617                 dbus_connection_close(bus);
618                 dbus_connection_unref(bus);
619         }
620
621         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
622 }