chiark / gitweb /
install: make InstallContext::{will_install,have_installed} OrderedHashmaps
[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 <errno.h>
23 #include <string.h>
24 #include <unistd.h>
25 #include <sys/capability.h>
26
27 #include "sd-id128.h"
28 #include "sd-messages.h"
29 #include "sd-event.h"
30 #include "sd-bus.h"
31
32 #include "util.h"
33 #include "strv.h"
34 #include "def.h"
35 #include "clock-util.h"
36 #include "conf-files.h"
37 #include "path-util.h"
38 #include "fileio-label.h"
39 #include "label.h"
40 #include "bus-util.h"
41 #include "bus-errors.h"
42 #include "event-util.h"
43
44 #define NULL_ADJTIME_UTC "0.0 0 0\n0\nUTC\n"
45 #define NULL_ADJTIME_LOCAL "0.0 0 0\n0\nLOCAL\n"
46
47 typedef struct Context {
48         char *zone;
49         bool local_rtc;
50         bool can_ntp;
51         bool use_ntp;
52         Hashmap *polkit_registry;
53 } Context;
54
55 static void context_free(Context *c) {
56         assert(c);
57
58         free(c->zone);
59         bus_verify_polkit_async_registry_free(c->polkit_registry);
60 }
61
62 static int context_read_data(Context *c) {
63         _cleanup_free_ char *t = NULL;
64         int r;
65
66         assert(c);
67
68         r = readlink_malloc("/etc/localtime", &t);
69         if (r < 0) {
70                 if (r == -EINVAL)
71                         log_warning("/etc/localtime should be a symbolic link to a time zone data file in /usr/share/zoneinfo/.");
72                 else
73                         log_warning("Failed to get target of /etc/localtime: %s", strerror(-r));
74         } else {
75                 const char *e;
76
77                 e = path_startswith(t, "/usr/share/zoneinfo/");
78                 if (!e)
79                         e = path_startswith(t, "../usr/share/zoneinfo/");
80
81                 if (!e)
82                         log_warning("/etc/localtime should be a symbolic link to a time zone data file in /usr/share/zoneinfo/.");
83                 else {
84                         c->zone = strdup(e);
85                         if (!c->zone)
86                                 return log_oom();
87
88                         goto have_timezone;
89                 }
90         }
91
92 have_timezone:
93         if (isempty(c->zone)) {
94                 free(c->zone);
95                 c->zone = NULL;
96         }
97
98         c->local_rtc = clock_is_localtime() > 0;
99
100         return 0;
101 }
102
103 static int context_write_data_timezone(Context *c) {
104         _cleanup_free_ char *p = NULL;
105         int r = 0;
106
107         assert(c);
108
109         if (isempty(c->zone)) {
110                 if (unlink("/etc/localtime") < 0 && errno != ENOENT)
111                         r = -errno;
112
113                 return r;
114         }
115
116         p = strappend("../usr/share/zoneinfo/", c->zone);
117         if (!p)
118                 return log_oom();
119
120         r = symlink_atomic(p, "/etc/localtime");
121         if (r < 0)
122                 return r;
123
124         return 0;
125 }
126
127 static int context_write_data_local_rtc(Context *c) {
128         int r;
129         _cleanup_free_ char *s = NULL, *w = NULL;
130
131         assert(c);
132
133         r = read_full_file("/etc/adjtime", &s, NULL);
134         if (r < 0) {
135                 if (r != -ENOENT)
136                         return r;
137
138                 if (!c->local_rtc)
139                         return 0;
140
141                 w = strdup(NULL_ADJTIME_LOCAL);
142                 if (!w)
143                         return -ENOMEM;
144         } else {
145                 char *p, *e;
146                 size_t a, b;
147
148                 p = strchr(s, '\n');
149                 if (!p)
150                         return -EIO;
151
152                 p = strchr(p+1, '\n');
153                 if (!p)
154                         return -EIO;
155
156                 p++;
157                 e = strchr(p, '\n');
158                 if (!e)
159                         return -EIO;
160
161                 a = p - s;
162                 b = strlen(e);
163
164                 w = new(char, a + (c->local_rtc ? 5 : 3) + b + 1);
165                 if (!w)
166                         return -ENOMEM;
167
168                 *(char*) mempcpy(stpcpy(mempcpy(w, s, a), c->local_rtc ? "LOCAL" : "UTC"), e, b) = 0;
169
170                 if (streq(w, NULL_ADJTIME_UTC)) {
171                         if (unlink("/etc/adjtime") < 0)
172                                 if (errno != ENOENT)
173                                         return -errno;
174
175                         return 0;
176                 }
177         }
178
179         mac_selinux_init("/etc");
180         return write_string_file_atomic_label("/etc/adjtime", w);
181 }
182
183 static int context_read_ntp(Context *c, sd_bus *bus) {
184         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
185         sd_bus_message *reply = NULL;
186         const char *s;
187         int r;
188
189         assert(c);
190         assert(bus);
191
192         r = sd_bus_call_method(
193                         bus,
194                         "org.freedesktop.systemd1",
195                         "/org/freedesktop/systemd1",
196                         "org.freedesktop.systemd1.Manager",
197                         "GetUnitFileState",
198                         &error,
199                         &reply,
200                         "s",
201                         "systemd-timesyncd.service");
202
203         if (r < 0) {
204                 if (sd_bus_error_has_name(&error, SD_BUS_ERROR_FILE_NOT_FOUND) ||
205                     sd_bus_error_has_name(&error, "org.freedesktop.systemd1.LoadFailed") ||
206                     sd_bus_error_has_name(&error, "org.freedesktop.systemd1.NoSuchUnit"))
207                         return 0;
208
209                 return r;
210         }
211
212         r = sd_bus_message_read(reply, "s", &s);
213         if (r < 0)
214                 return r;
215
216         c->can_ntp = true;
217         c->use_ntp = STR_IN_SET(s, "enabled", "enabled-runtime");
218
219         return 0;
220 }
221
222 static int context_start_ntp(Context *c, sd_bus *bus, sd_bus_error *error) {
223         int r;
224
225         assert(c);
226         assert(bus);
227         assert(error);
228
229         if (c->use_ntp)
230                 r = sd_bus_call_method(
231                                 bus,
232                                 "org.freedesktop.systemd1",
233                                 "/org/freedesktop/systemd1",
234                                 "org.freedesktop.systemd1.Manager",
235                                 "StartUnit",
236                                 error,
237                                 NULL,
238                                 "ss",
239                                 "systemd-timesyncd.service",
240                                 "replace");
241         else
242                 r = sd_bus_call_method(
243                                 bus,
244                                 "org.freedesktop.systemd1",
245                                 "/org/freedesktop/systemd1",
246                                 "org.freedesktop.systemd1.Manager",
247                                 "StopUnit",
248                                 error,
249                                 NULL,
250                                 "ss",
251                                 "systemd-timesyncd.service",
252                                 "replace");
253
254         if (r < 0) {
255                 if (sd_bus_error_has_name(error, SD_BUS_ERROR_FILE_NOT_FOUND) ||
256                     sd_bus_error_has_name(error, "org.freedesktop.systemd1.LoadFailed") ||
257                     sd_bus_error_has_name(error, "org.freedesktop.systemd1.NoSuchUnit")) {
258                         sd_bus_error_set_const(error, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported.");
259                         return -ENOTSUP;
260                 }
261
262                 return r;
263         }
264
265         return 0;
266 }
267
268 static int context_enable_ntp(Context*c, sd_bus *bus, sd_bus_error *error) {
269         int r;
270
271         assert(c);
272         assert(bus);
273         assert(error);
274
275         if (c->use_ntp)
276                 r = sd_bus_call_method(
277                                 bus,
278                                 "org.freedesktop.systemd1",
279                                 "/org/freedesktop/systemd1",
280                                 "org.freedesktop.systemd1.Manager",
281                                 "EnableUnitFiles",
282                                 error,
283                                 NULL,
284                                 "asbb", 1,
285                                 "systemd-timesyncd.service",
286                                 false, true);
287         else
288                 r = sd_bus_call_method(
289                                 bus,
290                                 "org.freedesktop.systemd1",
291                                 "/org/freedesktop/systemd1",
292                                 "org.freedesktop.systemd1.Manager",
293                                 "DisableUnitFiles",
294                                 error,
295                                 NULL,
296                                 "asb", 1,
297                                 "systemd-timesyncd.service",
298                                 false);
299
300         if (r < 0) {
301                 if (sd_bus_error_has_name(error, SD_BUS_ERROR_FILE_NOT_FOUND)) {
302                         sd_bus_error_set_const(error, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported.");
303                         return -ENOTSUP;
304                 }
305
306                 return r;
307         }
308
309         r = sd_bus_call_method(
310                         bus,
311                         "org.freedesktop.systemd1",
312                         "/org/freedesktop/systemd1",
313                         "org.freedesktop.systemd1.Manager",
314                         "Reload",
315                         error,
316                         NULL,
317                         NULL);
318         if (r < 0)
319                 return r;
320
321         return 0;
322 }
323
324 static int property_get_rtc_time(
325                 sd_bus *bus,
326                 const char *path,
327                 const char *interface,
328                 const char *property,
329                 sd_bus_message *reply,
330                 void *userdata,
331                 sd_bus_error *error) {
332
333         struct tm tm;
334         usec_t t;
335         int r;
336
337         zero(tm);
338         r = clock_get_hwclock(&tm);
339         if (r == -EBUSY) {
340                 log_warning("/dev/rtc is busy. Is somebody keeping it open continuously? That's not a good idea... Returning a bogus RTC timestamp.");
341                 t = 0;
342         } else if (r == -ENOENT) {
343                 log_debug("/dev/rtc not found.");
344                 t = 0; /* no RTC found */
345         } else if (r < 0)
346                 return sd_bus_error_set_errnof(error, r, "Failed to read RTC: %s", strerror(-r));
347         else
348                 t = (usec_t) timegm(&tm) * USEC_PER_SEC;
349
350         return sd_bus_message_append(reply, "t", t);
351 }
352
353 static int property_get_time(
354                 sd_bus *bus,
355                 const char *path,
356                 const char *interface,
357                 const char *property,
358                 sd_bus_message *reply,
359                 void *userdata,
360                 sd_bus_error *error) {
361
362         return sd_bus_message_append(reply, "t", now(CLOCK_REALTIME));
363 }
364
365 static int property_get_ntp_sync(
366                 sd_bus *bus,
367                 const char *path,
368                 const char *interface,
369                 const char *property,
370                 sd_bus_message *reply,
371                 void *userdata,
372                 sd_bus_error *error) {
373
374         return sd_bus_message_append(reply, "b", ntp_synced());
375 }
376
377 static int method_set_timezone(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
378         Context *c = userdata;
379         const char *z;
380         int interactive;
381         char *t;
382         int r;
383
384         assert(bus);
385         assert(m);
386         assert(c);
387
388         r = sd_bus_message_read(m, "sb", &z, &interactive);
389         if (r < 0)
390                 return r;
391
392         if (!timezone_is_valid(z))
393                 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid time zone '%s'", z);
394
395         if (streq_ptr(z, c->zone))
396                 return sd_bus_reply_method_return(m, NULL);
397
398         r = bus_verify_polkit_async(m, CAP_SYS_TIME, "org.freedesktop.timedate1.set-timezone", interactive, &c->polkit_registry, error);
399         if (r < 0)
400                 return r;
401         if (r == 0)
402                 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
403
404         t = strdup(z);
405         if (!t)
406                 return -ENOMEM;
407
408         free(c->zone);
409         c->zone = t;
410
411         /* 1. Write new configuration file */
412         r = context_write_data_timezone(c);
413         if (r < 0) {
414                 log_error("Failed to set time zone: %s", strerror(-r));
415                 return sd_bus_error_set_errnof(error, r, "Failed to set time zone: %s", strerror(-r));
416         }
417
418         /* 2. Tell the kernel our timezone */
419         clock_set_timezone(NULL);
420
421         if (c->local_rtc) {
422                 struct timespec ts;
423                 struct tm *tm;
424
425                 /* 3. Sync RTC from system clock, with the new delta */
426                 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
427                 assert_se(tm = localtime(&ts.tv_sec));
428                 clock_set_hwclock(tm);
429         }
430
431         log_struct(LOG_INFO,
432                    MESSAGE_ID(SD_MESSAGE_TIMEZONE_CHANGE),
433                    "TIMEZONE=%s", c->zone,
434                    "MESSAGE=Changed time zone to '%s'.", c->zone,
435                    NULL);
436
437         sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "Timezone", NULL);
438
439         return sd_bus_reply_method_return(m, NULL);
440 }
441
442 static int method_set_local_rtc(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
443         int lrtc, fix_system, interactive;
444         Context *c = userdata;
445         struct timespec ts;
446         int r;
447
448         assert(bus);
449         assert(m);
450         assert(c);
451
452         r = sd_bus_message_read(m, "bbb", &lrtc, &fix_system, &interactive);
453         if (r < 0)
454                 return r;
455
456         if (lrtc == c->local_rtc)
457                 return sd_bus_reply_method_return(m, NULL);
458
459         r = bus_verify_polkit_async(m, CAP_SYS_TIME, "org.freedesktop.timedate1.set-local-rtc", interactive, &c->polkit_registry, error);
460         if (r < 0)
461                 return r;
462         if (r == 0)
463                 return 1;
464
465         c->local_rtc = lrtc;
466
467         /* 1. Write new configuration file */
468         r = context_write_data_local_rtc(c);
469         if (r < 0) {
470                 log_error("Failed to set RTC to local/UTC: %s", strerror(-r));
471                 return sd_bus_error_set_errnof(error, r, "Failed to set RTC to local/UTC: %s", strerror(-r));
472         }
473
474         /* 2. Tell the kernel our timezone */
475         clock_set_timezone(NULL);
476
477         /* 3. Synchronize clocks */
478         assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
479
480         if (fix_system) {
481                 struct tm tm;
482
483                 /* Sync system clock from RTC; first,
484                  * initialize the timezone fields of
485                  * struct tm. */
486                 if (c->local_rtc)
487                         tm = *localtime(&ts.tv_sec);
488                 else
489                         tm = *gmtime(&ts.tv_sec);
490
491                 /* Override the main fields of
492                  * struct tm, but not the timezone
493                  * fields */
494                 if (clock_get_hwclock(&tm) >= 0) {
495
496                         /* And set the system clock
497                          * with this */
498                         if (c->local_rtc)
499                                 ts.tv_sec = mktime(&tm);
500                         else
501                                 ts.tv_sec = timegm(&tm);
502
503                         clock_settime(CLOCK_REALTIME, &ts);
504                 }
505
506         } else {
507                 struct tm *tm;
508
509                 /* Sync RTC from system clock */
510                 if (c->local_rtc)
511                         tm = localtime(&ts.tv_sec);
512                 else
513                         tm = gmtime(&ts.tv_sec);
514
515                 clock_set_hwclock(tm);
516         }
517
518         log_info("RTC configured to %s time.", c->local_rtc ? "local" : "UTC");
519
520         sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "LocalRTC", NULL);
521
522         return sd_bus_reply_method_return(m, NULL);
523 }
524
525 static int method_set_time(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
526         int relative, interactive;
527         Context *c = userdata;
528         int64_t utc;
529         struct timespec ts;
530         struct tm* tm;
531         int r;
532
533         assert(bus);
534         assert(m);
535         assert(c);
536
537         if (c->use_ntp)
538                 return sd_bus_error_setf(error, BUS_ERROR_AUTOMATIC_TIME_SYNC_ENABLED, "Automatic time synchronization is enabled");
539
540         r = sd_bus_message_read(m, "xbb", &utc, &relative, &interactive);
541         if (r < 0)
542                 return r;
543
544         if (!relative && utc <= 0)
545                 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid absolute time");
546
547         if (relative && utc == 0)
548                 return sd_bus_reply_method_return(m, NULL);
549
550         if (relative) {
551                 usec_t n, x;
552
553                 n = now(CLOCK_REALTIME);
554                 x = n + utc;
555
556                 if ((utc > 0 && x < n) ||
557                     (utc < 0 && x > n))
558                         return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Time value overflow");
559
560                 timespec_store(&ts, x);
561         } else
562                 timespec_store(&ts, (usec_t) utc);
563
564         r = bus_verify_polkit_async(m, CAP_SYS_TIME, "org.freedesktop.timedate1.set-time", interactive, &c->polkit_registry, error);
565         if (r < 0)
566                 return r;
567         if (r == 0)
568                 return 1;
569
570         /* Set system clock */
571         if (clock_settime(CLOCK_REALTIME, &ts) < 0) {
572                 log_error("Failed to set local time: %m");
573                 return sd_bus_error_set_errnof(error, errno, "Failed to set local time: %m");
574         }
575
576         /* Sync down to RTC */
577         if (c->local_rtc)
578                 tm = localtime(&ts.tv_sec);
579         else
580                 tm = gmtime(&ts.tv_sec);
581         clock_set_hwclock(tm);
582
583         log_struct(LOG_INFO,
584                    MESSAGE_ID(SD_MESSAGE_TIME_CHANGE),
585                    "REALTIME="USEC_FMT, timespec_load(&ts),
586                    "MESSAGE=Changed local time to %s", ctime(&ts.tv_sec),
587                    NULL);
588
589         return sd_bus_reply_method_return(m, NULL);
590 }
591
592 static int method_set_ntp(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
593         int ntp, interactive;
594         Context *c = userdata;
595         int r;
596
597         r = sd_bus_message_read(m, "bb", &ntp, &interactive);
598         if (r < 0)
599                 return r;
600
601         if ((bool)ntp == c->use_ntp)
602                 return sd_bus_reply_method_return(m, NULL);
603
604         r = bus_verify_polkit_async(m, CAP_SYS_TIME, "org.freedesktop.timedate1.set-ntp", interactive, &c->polkit_registry, error);
605         if (r < 0)
606                 return r;
607         if (r == 0)
608                 return 1;
609
610         c->use_ntp = ntp;
611
612         r = context_enable_ntp(c, bus, error);
613         if (r < 0)
614                 return r;
615
616         r = context_start_ntp(c, bus, error);
617         if (r < 0)
618                 return r;
619
620         log_info("Set NTP to %s", c->use_ntp ? "enabled" : "disabled");
621
622         sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "NTP", NULL);
623
624         return sd_bus_reply_method_return(m, NULL);
625 }
626
627 static const sd_bus_vtable timedate_vtable[] = {
628         SD_BUS_VTABLE_START(0),
629         SD_BUS_PROPERTY("Timezone", "s", NULL, offsetof(Context, zone), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
630         SD_BUS_PROPERTY("LocalRTC", "b", bus_property_get_bool, offsetof(Context, local_rtc), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
631         SD_BUS_PROPERTY("CanNTP", "b", bus_property_get_bool, offsetof(Context, can_ntp), 0),
632         SD_BUS_PROPERTY("NTP", "b", bus_property_get_bool, offsetof(Context, use_ntp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
633         SD_BUS_PROPERTY("NTPSynchronized", "b", property_get_ntp_sync, 0, 0),
634         SD_BUS_PROPERTY("TimeUSec", "t", property_get_time, 0, 0),
635         SD_BUS_PROPERTY("RTCTimeUSec", "t", property_get_rtc_time, 0, 0),
636         SD_BUS_METHOD("SetTime", "xbb", NULL, method_set_time, SD_BUS_VTABLE_UNPRIVILEGED),
637         SD_BUS_METHOD("SetTimezone", "sb", NULL, method_set_timezone, SD_BUS_VTABLE_UNPRIVILEGED),
638         SD_BUS_METHOD("SetLocalRTC", "bbb", NULL, method_set_local_rtc, SD_BUS_VTABLE_UNPRIVILEGED),
639         SD_BUS_METHOD("SetNTP", "bb", NULL, method_set_ntp, SD_BUS_VTABLE_UNPRIVILEGED),
640         SD_BUS_VTABLE_END,
641 };
642
643 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
644         _cleanup_bus_close_unref_ sd_bus *bus = NULL;
645         int r;
646
647         assert(c);
648         assert(event);
649         assert(_bus);
650
651         r = sd_bus_default_system(&bus);
652         if (r < 0) {
653                 log_error("Failed to get system bus connection: %s", strerror(-r));
654                 return r;
655         }
656
657         r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", timedate_vtable, c);
658         if (r < 0) {
659                 log_error("Failed to register object: %s", strerror(-r));
660                 return r;
661         }
662
663         r = sd_bus_request_name(bus, "org.freedesktop.timedate1", 0);
664         if (r < 0) {
665                 log_error("Failed to register name: %s", strerror(-r));
666                 return r;
667         }
668
669         r = sd_bus_attach_event(bus, event, 0);
670         if (r < 0) {
671                 log_error("Failed to attach bus to event loop: %s", strerror(-r));
672                 return r;
673         }
674
675         *_bus = bus;
676         bus = NULL;
677
678         return 0;
679 }
680
681 int main(int argc, char *argv[]) {
682         Context context = {};
683         _cleanup_event_unref_ sd_event *event = NULL;
684         _cleanup_bus_close_unref_ sd_bus *bus = NULL;
685         int r;
686
687         log_set_target(LOG_TARGET_AUTO);
688         log_parse_environment();
689         log_open();
690
691         umask(0022);
692
693         if (argc != 1) {
694                 log_error("This program takes no arguments.");
695                 r = -EINVAL;
696                 goto finish;
697         }
698
699         r = sd_event_default(&event);
700         if (r < 0) {
701                 log_error("Failed to allocate event loop: %s", strerror(-r));
702                 goto finish;
703         }
704
705         sd_event_set_watchdog(event, true);
706
707         r = connect_bus(&context, event, &bus);
708         if (r < 0)
709                 goto finish;
710
711         r = context_read_data(&context);
712         if (r < 0) {
713                 log_error("Failed to read time zone data: %s", strerror(-r));
714                 goto finish;
715         }
716
717         r = context_read_ntp(&context, bus);
718         if (r < 0) {
719                 log_error("Failed to determine whether NTP is enabled: %s", strerror(-r));
720                 goto finish;
721         }
722
723         r = bus_event_loop_with_idle(event, bus, "org.freedesktop.timedate1", DEFAULT_EXIT_USEC, NULL, NULL);
724         if (r < 0) {
725                 log_error("Failed to run event loop: %s", strerror(-r));
726                 goto finish;
727         }
728
729 finish:
730         context_free(&context);
731
732         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
733 }