]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/timedate/timedated.c
Merge pull request #362 from d-hatayama/fix_selinux_unit_check_v2
[thirdparty/systemd.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
26 #include "sd-messages.h"
27 #include "sd-event.h"
28 #include "sd-bus.h"
29
30 #include "util.h"
31 #include "strv.h"
32 #include "def.h"
33 #include "clock-util.h"
34 #include "path-util.h"
35 #include "fileio-label.h"
36 #include "bus-util.h"
37 #include "bus-error.h"
38 #include "bus-common-errors.h"
39 #include "event-util.h"
40 #include "selinux-util.h"
41
42 #define NULL_ADJTIME_UTC "0.0 0 0\n0\nUTC\n"
43 #define NULL_ADJTIME_LOCAL "0.0 0 0\n0\nLOCAL\n"
44
45 static BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map timedated_errors[] = {
46 SD_BUS_ERROR_MAP("org.freedesktop.timedate1.NoNTPSupport", EOPNOTSUPP),
47 SD_BUS_ERROR_MAP_END
48 };
49
50 typedef struct Context {
51 char *zone;
52 bool local_rtc;
53 bool can_ntp;
54 bool use_ntp;
55 Hashmap *polkit_registry;
56 } Context;
57
58 static void context_free(Context *c) {
59 assert(c);
60
61 free(c->zone);
62 bus_verify_polkit_async_registry_free(c->polkit_registry);
63 }
64
65 static int context_read_data(Context *c) {
66 _cleanup_free_ char *t = NULL;
67 int r;
68
69 assert(c);
70
71 r = readlink_malloc("/etc/localtime", &t);
72 if (r < 0) {
73 if (r == -EINVAL)
74 log_warning("/etc/localtime should be a symbolic link to a time zone data file in /usr/share/zoneinfo/.");
75 else
76 log_warning_errno(r, "Failed to get target of /etc/localtime: %m");
77 } else {
78 const char *e;
79
80 e = path_startswith(t, "/usr/share/zoneinfo/");
81 if (!e)
82 e = path_startswith(t, "../usr/share/zoneinfo/");
83
84 if (!e)
85 log_warning("/etc/localtime should be a symbolic link to a time zone data file in /usr/share/zoneinfo/.");
86 else {
87 c->zone = strdup(e);
88 if (!c->zone)
89 return log_oom();
90 }
91 }
92
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 _cleanup_bus_message_unref_ 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(sd_bus *bus, sd_bus_error *error, bool enabled) {
223 int r;
224
225 assert(bus);
226 assert(error);
227
228 r = sd_bus_call_method(
229 bus,
230 "org.freedesktop.systemd1",
231 "/org/freedesktop/systemd1",
232 "org.freedesktop.systemd1.Manager",
233 enabled ? "StartUnit" : "StopUnit",
234 error,
235 NULL,
236 "ss",
237 "systemd-timesyncd.service",
238 "replace");
239 if (r < 0) {
240 if (sd_bus_error_has_name(error, SD_BUS_ERROR_FILE_NOT_FOUND) ||
241 sd_bus_error_has_name(error, "org.freedesktop.systemd1.LoadFailed") ||
242 sd_bus_error_has_name(error, "org.freedesktop.systemd1.NoSuchUnit"))
243 return sd_bus_error_set_const(error, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported.");
244
245 return r;
246 }
247
248 return 0;
249 }
250
251 static int context_enable_ntp(sd_bus *bus, sd_bus_error *error, bool enabled) {
252 int r;
253
254 assert(bus);
255 assert(error);
256
257 if (enabled)
258 r = sd_bus_call_method(
259 bus,
260 "org.freedesktop.systemd1",
261 "/org/freedesktop/systemd1",
262 "org.freedesktop.systemd1.Manager",
263 "EnableUnitFiles",
264 error,
265 NULL,
266 "asbb", 1,
267 "systemd-timesyncd.service",
268 false, true);
269 else
270 r = sd_bus_call_method(
271 bus,
272 "org.freedesktop.systemd1",
273 "/org/freedesktop/systemd1",
274 "org.freedesktop.systemd1.Manager",
275 "DisableUnitFiles",
276 error,
277 NULL,
278 "asb", 1,
279 "systemd-timesyncd.service",
280 false);
281
282 if (r < 0) {
283 if (sd_bus_error_has_name(error, SD_BUS_ERROR_FILE_NOT_FOUND))
284 return sd_bus_error_set_const(error, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported.");
285
286 return r;
287 }
288
289 r = sd_bus_call_method(
290 bus,
291 "org.freedesktop.systemd1",
292 "/org/freedesktop/systemd1",
293 "org.freedesktop.systemd1.Manager",
294 "Reload",
295 error,
296 NULL,
297 NULL);
298 if (r < 0)
299 return r;
300
301 return 0;
302 }
303
304 static int property_get_rtc_time(
305 sd_bus *bus,
306 const char *path,
307 const char *interface,
308 const char *property,
309 sd_bus_message *reply,
310 void *userdata,
311 sd_bus_error *error) {
312
313 struct tm tm;
314 usec_t t;
315 int r;
316
317 zero(tm);
318 r = clock_get_hwclock(&tm);
319 if (r == -EBUSY) {
320 log_warning("/dev/rtc is busy. Is somebody keeping it open continuously? That's not a good idea... Returning a bogus RTC timestamp.");
321 t = 0;
322 } else if (r == -ENOENT) {
323 log_debug("/dev/rtc not found.");
324 t = 0; /* no RTC found */
325 } else if (r < 0)
326 return sd_bus_error_set_errnof(error, r, "Failed to read RTC: %m");
327 else
328 t = (usec_t) timegm(&tm) * USEC_PER_SEC;
329
330 return sd_bus_message_append(reply, "t", t);
331 }
332
333 static int property_get_time(
334 sd_bus *bus,
335 const char *path,
336 const char *interface,
337 const char *property,
338 sd_bus_message *reply,
339 void *userdata,
340 sd_bus_error *error) {
341
342 return sd_bus_message_append(reply, "t", now(CLOCK_REALTIME));
343 }
344
345 static int property_get_ntp_sync(
346 sd_bus *bus,
347 const char *path,
348 const char *interface,
349 const char *property,
350 sd_bus_message *reply,
351 void *userdata,
352 sd_bus_error *error) {
353
354 return sd_bus_message_append(reply, "b", ntp_synced());
355 }
356
357 static int method_set_timezone(sd_bus_message *m, void *userdata, sd_bus_error *error) {
358 Context *c = userdata;
359 const char *z;
360 int interactive;
361 char *t;
362 int r;
363
364 assert(m);
365 assert(c);
366
367 r = sd_bus_message_read(m, "sb", &z, &interactive);
368 if (r < 0)
369 return r;
370
371 if (!timezone_is_valid(z))
372 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid time zone '%s'", z);
373
374 if (streq_ptr(z, c->zone))
375 return sd_bus_reply_method_return(m, NULL);
376
377 r = bus_verify_polkit_async(
378 m,
379 CAP_SYS_TIME,
380 "org.freedesktop.timedate1.set-timezone",
381 interactive,
382 UID_INVALID,
383 &c->polkit_registry,
384 error);
385 if (r < 0)
386 return r;
387 if (r == 0)
388 return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
389
390 t = strdup(z);
391 if (!t)
392 return -ENOMEM;
393
394 free(c->zone);
395 c->zone = t;
396
397 /* 1. Write new configuration file */
398 r = context_write_data_timezone(c);
399 if (r < 0) {
400 log_error_errno(r, "Failed to set time zone: %m");
401 return sd_bus_error_set_errnof(error, r, "Failed to set time zone: %m");
402 }
403
404 /* 2. Tell the kernel our timezone */
405 clock_set_timezone(NULL);
406
407 if (c->local_rtc) {
408 struct timespec ts;
409 struct tm *tm;
410
411 /* 3. Sync RTC from system clock, with the new delta */
412 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
413 assert_se(tm = localtime(&ts.tv_sec));
414 clock_set_hwclock(tm);
415 }
416
417 log_struct(LOG_INFO,
418 LOG_MESSAGE_ID(SD_MESSAGE_TIMEZONE_CHANGE),
419 "TIMEZONE=%s", c->zone,
420 LOG_MESSAGE("Changed time zone to '%s'.", c->zone),
421 NULL);
422
423 (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "Timezone", NULL);
424
425 return sd_bus_reply_method_return(m, NULL);
426 }
427
428 static int method_set_local_rtc(sd_bus_message *m, void *userdata, sd_bus_error *error) {
429 int lrtc, fix_system, interactive;
430 Context *c = userdata;
431 struct timespec ts;
432 int r;
433
434 assert(m);
435 assert(c);
436
437 r = sd_bus_message_read(m, "bbb", &lrtc, &fix_system, &interactive);
438 if (r < 0)
439 return r;
440
441 if (lrtc == c->local_rtc)
442 return sd_bus_reply_method_return(m, NULL);
443
444 r = bus_verify_polkit_async(
445 m,
446 CAP_SYS_TIME,
447 "org.freedesktop.timedate1.set-local-rtc",
448 interactive,
449 UID_INVALID,
450 &c->polkit_registry,
451 error);
452 if (r < 0)
453 return r;
454 if (r == 0)
455 return 1;
456
457 c->local_rtc = lrtc;
458
459 /* 1. Write new configuration file */
460 r = context_write_data_local_rtc(c);
461 if (r < 0) {
462 log_error_errno(r, "Failed to set RTC to local/UTC: %m");
463 return sd_bus_error_set_errnof(error, r, "Failed to set RTC to local/UTC: %m");
464 }
465
466 /* 2. Tell the kernel our timezone */
467 clock_set_timezone(NULL);
468
469 /* 3. Synchronize clocks */
470 assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0);
471
472 if (fix_system) {
473 struct tm tm;
474
475 /* Sync system clock from RTC; first,
476 * initialize the timezone fields of
477 * struct tm. */
478 if (c->local_rtc)
479 tm = *localtime(&ts.tv_sec);
480 else
481 tm = *gmtime(&ts.tv_sec);
482
483 /* Override the main fields of
484 * struct tm, but not the timezone
485 * fields */
486 if (clock_get_hwclock(&tm) >= 0) {
487
488 /* And set the system clock
489 * with this */
490 if (c->local_rtc)
491 ts.tv_sec = mktime(&tm);
492 else
493 ts.tv_sec = timegm(&tm);
494
495 clock_settime(CLOCK_REALTIME, &ts);
496 }
497
498 } else {
499 struct tm *tm;
500
501 /* Sync RTC from system clock */
502 if (c->local_rtc)
503 tm = localtime(&ts.tv_sec);
504 else
505 tm = gmtime(&ts.tv_sec);
506
507 clock_set_hwclock(tm);
508 }
509
510 log_info("RTC configured to %s time.", c->local_rtc ? "local" : "UTC");
511
512 (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "LocalRTC", NULL);
513
514 return sd_bus_reply_method_return(m, NULL);
515 }
516
517 static int method_set_time(sd_bus_message *m, void *userdata, sd_bus_error *error) {
518 int relative, interactive;
519 Context *c = userdata;
520 int64_t utc;
521 struct timespec ts;
522 usec_t start;
523 struct tm* tm;
524 int r;
525
526 assert(m);
527 assert(c);
528
529 if (c->use_ntp)
530 return sd_bus_error_setf(error, BUS_ERROR_AUTOMATIC_TIME_SYNC_ENABLED, "Automatic time synchronization is enabled");
531
532 /* this only gets used if dbus does not provide a timestamp */
533 start = now(CLOCK_MONOTONIC);
534
535 r = sd_bus_message_read(m, "xbb", &utc, &relative, &interactive);
536 if (r < 0)
537 return r;
538
539 if (!relative && utc <= 0)
540 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid absolute time");
541
542 if (relative && utc == 0)
543 return sd_bus_reply_method_return(m, NULL);
544
545 if (relative) {
546 usec_t n, x;
547
548 n = now(CLOCK_REALTIME);
549 x = n + utc;
550
551 if ((utc > 0 && x < n) ||
552 (utc < 0 && x > n))
553 return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Time value overflow");
554
555 timespec_store(&ts, x);
556 } else
557 timespec_store(&ts, (usec_t) utc);
558
559 r = bus_verify_polkit_async(
560 m,
561 CAP_SYS_TIME,
562 "org.freedesktop.timedate1.set-time",
563 interactive,
564 UID_INVALID,
565 &c->polkit_registry,
566 error);
567 if (r < 0)
568 return r;
569 if (r == 0)
570 return 1;
571
572 /* adjust ts for time spent in program */
573 r = sd_bus_message_get_monotonic_usec(m, &start);
574 /* when sd_bus_message_get_monotonic_usec() returns -ENODATA it does not modify &start */
575 if (r < 0 && r != -ENODATA)
576 return r;
577
578 timespec_store(&ts, timespec_load(&ts) + (now(CLOCK_MONOTONIC) - start));
579
580 /* Set system clock */
581 if (clock_settime(CLOCK_REALTIME, &ts) < 0) {
582 log_error_errno(errno, "Failed to set local time: %m");
583 return sd_bus_error_set_errnof(error, errno, "Failed to set local time: %m");
584 }
585
586 /* Sync down to RTC */
587 if (c->local_rtc)
588 tm = localtime(&ts.tv_sec);
589 else
590 tm = gmtime(&ts.tv_sec);
591 clock_set_hwclock(tm);
592
593 log_struct(LOG_INFO,
594 LOG_MESSAGE_ID(SD_MESSAGE_TIME_CHANGE),
595 "REALTIME="USEC_FMT, timespec_load(&ts),
596 LOG_MESSAGE("Changed local time to %s", ctime(&ts.tv_sec)),
597 NULL);
598
599 return sd_bus_reply_method_return(m, NULL);
600 }
601
602 static int method_set_ntp(sd_bus_message *m, void *userdata, sd_bus_error *error) {
603 int enabled, interactive;
604 Context *c = userdata;
605 int r;
606
607 assert(m);
608 assert(c);
609
610 r = sd_bus_message_read(m, "bb", &enabled, &interactive);
611 if (r < 0)
612 return r;
613
614 if ((bool)enabled == c->use_ntp)
615 return sd_bus_reply_method_return(m, NULL);
616
617 r = bus_verify_polkit_async(
618 m,
619 CAP_SYS_TIME,
620 "org.freedesktop.timedate1.set-ntp",
621 interactive,
622 UID_INVALID,
623 &c->polkit_registry,
624 error);
625 if (r < 0)
626 return r;
627 if (r == 0)
628 return 1;
629
630 r = context_enable_ntp(sd_bus_message_get_bus(m), error, enabled);
631 if (r < 0)
632 return r;
633
634 r = context_start_ntp(sd_bus_message_get_bus(m), error, enabled);
635 if (r < 0)
636 return r;
637
638 c->use_ntp = enabled;
639 log_info("Set NTP to %s", enabled ? "enabled" : "disabled");
640
641 (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "NTP", NULL);
642
643 return sd_bus_reply_method_return(m, NULL);
644 }
645
646 static const sd_bus_vtable timedate_vtable[] = {
647 SD_BUS_VTABLE_START(0),
648 SD_BUS_PROPERTY("Timezone", "s", NULL, offsetof(Context, zone), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
649 SD_BUS_PROPERTY("LocalRTC", "b", bus_property_get_bool, offsetof(Context, local_rtc), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
650 SD_BUS_PROPERTY("CanNTP", "b", bus_property_get_bool, offsetof(Context, can_ntp), 0),
651 SD_BUS_PROPERTY("NTP", "b", bus_property_get_bool, offsetof(Context, use_ntp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
652 SD_BUS_PROPERTY("NTPSynchronized", "b", property_get_ntp_sync, 0, 0),
653 SD_BUS_PROPERTY("TimeUSec", "t", property_get_time, 0, 0),
654 SD_BUS_PROPERTY("RTCTimeUSec", "t", property_get_rtc_time, 0, 0),
655 SD_BUS_METHOD("SetTime", "xbb", NULL, method_set_time, SD_BUS_VTABLE_UNPRIVILEGED),
656 SD_BUS_METHOD("SetTimezone", "sb", NULL, method_set_timezone, SD_BUS_VTABLE_UNPRIVILEGED),
657 SD_BUS_METHOD("SetLocalRTC", "bbb", NULL, method_set_local_rtc, SD_BUS_VTABLE_UNPRIVILEGED),
658 SD_BUS_METHOD("SetNTP", "bb", NULL, method_set_ntp, SD_BUS_VTABLE_UNPRIVILEGED),
659 SD_BUS_VTABLE_END,
660 };
661
662 static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) {
663 _cleanup_bus_flush_close_unref_ sd_bus *bus = NULL;
664 int r;
665
666 assert(c);
667 assert(event);
668 assert(_bus);
669
670 r = sd_bus_default_system(&bus);
671 if (r < 0)
672 return log_error_errno(r, "Failed to get system bus connection: %m");
673
674 r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", timedate_vtable, c);
675 if (r < 0)
676 return log_error_errno(r, "Failed to register object: %m");
677
678 r = sd_bus_request_name(bus, "org.freedesktop.timedate1", 0);
679 if (r < 0)
680 return log_error_errno(r, "Failed to register name: %m");
681
682 r = sd_bus_attach_event(bus, event, 0);
683 if (r < 0)
684 return log_error_errno(r, "Failed to attach bus to event loop: %m");
685
686 *_bus = bus;
687 bus = NULL;
688
689 return 0;
690 }
691
692 int main(int argc, char *argv[]) {
693 Context context = {};
694 _cleanup_event_unref_ sd_event *event = NULL;
695 _cleanup_bus_flush_close_unref_ sd_bus *bus = NULL;
696 int r;
697
698 log_set_target(LOG_TARGET_AUTO);
699 log_parse_environment();
700 log_open();
701
702 umask(0022);
703
704 if (argc != 1) {
705 log_error("This program takes no arguments.");
706 r = -EINVAL;
707 goto finish;
708 }
709
710 r = sd_event_default(&event);
711 if (r < 0) {
712 log_error_errno(r, "Failed to allocate event loop: %m");
713 goto finish;
714 }
715
716 sd_event_set_watchdog(event, true);
717
718 r = connect_bus(&context, event, &bus);
719 if (r < 0)
720 goto finish;
721
722 (void) sd_bus_negotiate_timestamp(bus, true);
723
724 r = context_read_data(&context);
725 if (r < 0) {
726 log_error_errno(r, "Failed to read time zone data: %m");
727 goto finish;
728 }
729
730 r = context_read_ntp(&context, bus);
731 if (r < 0) {
732 log_error_errno(r, "Failed to determine whether NTP is enabled: %m");
733 goto finish;
734 }
735
736 r = bus_event_loop_with_idle(event, bus, "org.freedesktop.timedate1", DEFAULT_EXIT_USEC, NULL, NULL);
737 if (r < 0) {
738 log_error_errno(r, "Failed to run event loop: %m");
739 goto finish;
740 }
741
742 finish:
743 context_free(&context);
744
745 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
746 }