]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/resolve-host/resolve-host.c
resolved: when passing RRs across the bus, make sure not to use name compression
[thirdparty/systemd.git] / src / resolve-host / resolve-host.c
CommitLineData
bdef7319
ZJS
1/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3/***
4 This file is part of systemd.
5
6 Copyright 2014 Zbigniew Jędrzejewski-Szmek
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
bdef7319
ZJS
22#include <net/if.h>
23#include <getopt.h>
24
25#include "sd-bus.h"
26#include "bus-util.h"
27#include "bus-error.h"
bdef7319
ZJS
28#include "in-addr-util.h"
29#include "af-list.h"
30#include "build.h"
31
2d4c5cbc 32#include "resolved-dns-packet.h"
51323288 33#include "resolved-def.h"
2d4c5cbc 34
bdef7319
ZJS
35#define DNS_CALL_TIMEOUT_USEC (45*USEC_PER_SEC)
36
37static int arg_family = AF_UNSPEC;
38static int arg_ifindex = 0;
de292aa1 39static int arg_type = 0;
2d4c5cbc 40static uint16_t arg_class = 0;
b93312f5 41static bool arg_legend = true;
51323288
LP
42static uint64_t arg_flags = 0;
43
78c6a153 44static void print_source(uint64_t flags, usec_t rtt) {
74998408 45 char rtt_str[FORMAT_TIMESTAMP_MAX];
51323288
LP
46
47 if (!arg_legend)
48 return;
49
78c6a153 50 if (flags == 0)
51323288
LP
51 return;
52
53 fputs("\n-- Information acquired via", stdout);
54
55 if (flags != 0)
56 printf(" protocol%s%s%s",
57 flags & SD_RESOLVED_DNS ? " DNS" :"",
58 flags & SD_RESOLVED_LLMNR_IPV4 ? " LLMNR/IPv4" : "",
59 flags & SD_RESOLVED_LLMNR_IPV6 ? " LLMNR/IPv6" : "");
60
74998408
TG
61 assert_se(format_timespan(rtt_str, sizeof(rtt_str), rtt, 100));
62
63 printf(" in %s", rtt_str);
64
51323288
LP
65 fputc('.', stdout);
66 fputc('\n', stdout);
67}
bdef7319 68
79266746 69static int resolve_host(sd_bus *bus, const char *name) {
bdef7319
ZJS
70
71 _cleanup_bus_message_unref_ sd_bus_message *req = NULL, *reply = NULL;
72 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
79266746 73 const char *canonical = NULL;
78c6a153 74 char ifname[IF_NAMESIZE] = "";
bdef7319 75 unsigned c = 0;
78c6a153 76 int r;
51323288 77 uint64_t flags;
74998408 78 usec_t ts;
bdef7319
ZJS
79
80 assert(name);
81
78c6a153
LP
82 if (arg_ifindex > 0 && !if_indextoname(arg_ifindex, ifname))
83 return log_error_errno(errno, "Failed to resolve interface name for index %i: %m", arg_ifindex);
84
85 log_debug("Resolving %s (family %s, interface %s).", name, af_to_name(arg_family) ?: "*", isempty(ifname) ? "*" : ifname);
bdef7319
ZJS
86
87 r = sd_bus_message_new_method_call(
88 bus,
89 &req,
90 "org.freedesktop.resolve1",
91 "/org/freedesktop/resolve1",
92 "org.freedesktop.resolve1.Manager",
93 "ResolveHostname");
02dd6e18
LP
94 if (r < 0)
95 return bus_log_create_error(r);
bdef7319 96
51323288 97 r = sd_bus_message_append(req, "isit", arg_ifindex, name, arg_family, arg_flags);
02dd6e18
LP
98 if (r < 0)
99 return bus_log_create_error(r);
bdef7319 100
74998408
TG
101 ts = now(CLOCK_MONOTONIC);
102
bdef7319
ZJS
103 r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply);
104 if (r < 0) {
105 log_error("%s: resolve call failed: %s", name, bus_error_message(&error, r));
106 return r;
107 }
108
74998408
TG
109 ts = now(CLOCK_MONOTONIC) - ts;
110
78c6a153 111 r = sd_bus_message_enter_container(reply, 'a', "(iiay)");
02dd6e18
LP
112 if (r < 0)
113 return bus_log_parse_error(r);
bdef7319 114
78c6a153 115 while ((r = sd_bus_message_enter_container(reply, 'r', "iiay")) > 0) {
bdef7319 116 const void *a;
bdef7319
ZJS
117 size_t sz;
118 _cleanup_free_ char *pretty = NULL;
78c6a153
LP
119 int ifindex, family;
120
121 assert_cc(sizeof(int) == sizeof(int32_t));
bdef7319 122
78c6a153 123 r = sd_bus_message_read(reply, "ii", &ifindex, &family);
02dd6e18
LP
124 if (r < 0)
125 return bus_log_parse_error(r);
bdef7319
ZJS
126
127 r = sd_bus_message_read_array(reply, 'y', &a, &sz);
02dd6e18
LP
128 if (r < 0)
129 return bus_log_parse_error(r);
bdef7319 130
bdef7319 131 r = sd_bus_message_exit_container(reply);
02dd6e18
LP
132 if (r < 0)
133 return bus_log_parse_error(r);
bdef7319 134
79266746 135 if (!IN_SET(family, AF_INET, AF_INET6)) {
be636413 136 log_debug("%s: skipping entry with family %d (%s)", name, family, af_to_name(family) ?: "unknown");
bdef7319
ZJS
137 continue;
138 }
139
140 if (sz != FAMILY_ADDRESS_SIZE(family)) {
78c6a153 141 log_error("%s: systemd-resolved returned address of invalid size %zu for family %s", name, sz, af_to_name(family) ?: "unknown");
bdef7319
ZJS
142 continue;
143 }
144
78c6a153
LP
145 ifname[0] = 0;
146 if (ifindex > 0 && !if_indextoname(ifindex, ifname))
147 log_warning_errno(errno, "Failed to resolve interface name for index %i: %m", ifindex);
bdef7319 148
bdef7319 149 r = in_addr_to_string(family, a, &pretty);
78c6a153
LP
150 if (r < 0)
151 return log_error_errno(r, "Failed to print address for %s: %m", name);
bdef7319 152
79266746
LP
153 printf("%*s%s %s%s%s\n",
154 (int) strlen(name), c == 0 ? name : "", c == 0 ? ":" : " ",
155 pretty,
156 isempty(ifname) ? "" : "%", ifname);
bdef7319
ZJS
157
158 c++;
159 }
79266746
LP
160 if (r < 0)
161 return bus_log_parse_error(r);
162
163 r = sd_bus_message_exit_container(reply);
164 if (r < 0)
165 return bus_log_parse_error(r);
166
51323288 167 r = sd_bus_message_read(reply, "st", &canonical, &flags);
79266746
LP
168 if (r < 0)
169 return bus_log_parse_error(r);
170
171 if (!streq(name, canonical)) {
172 printf("%*s%s (%s)\n",
173 (int) strlen(name), c == 0 ? name : "", c == 0 ? ":" : " ",
174 canonical);
175 }
bdef7319
ZJS
176
177 if (c == 0) {
178 log_error("%s: no addresses found", name);
79266746
LP
179 return -ESRCH;
180 }
181
78c6a153 182 print_source(flags, ts);
51323288 183
79266746
LP
184 return 0;
185}
186
187static int resolve_address(sd_bus *bus, int family, const union in_addr_union *address, int ifindex) {
188 _cleanup_bus_message_unref_ sd_bus_message *req = NULL, *reply = NULL;
189 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
190 _cleanup_free_ char *pretty = NULL;
191 char ifname[IF_NAMESIZE] = "";
51323288 192 uint64_t flags;
79266746 193 unsigned c = 0;
74998408 194 usec_t ts;
79266746
LP
195 int r;
196
197 assert(bus);
198 assert(IN_SET(family, AF_INET, AF_INET6));
199 assert(address);
200
78c6a153
LP
201 if (ifindex <= 0)
202 ifindex = arg_ifindex;
203
79266746
LP
204 r = in_addr_to_string(family, address, &pretty);
205 if (r < 0)
206 return log_oom();
207
78c6a153
LP
208 if (ifindex > 0 && !if_indextoname(ifindex, ifname))
209 return log_error_errno(errno, "Failed to resolve interface name for index %i: %m", ifindex);
79266746
LP
210
211 log_debug("Resolving %s%s%s.", pretty, isempty(ifname) ? "" : "%", ifname);
212
213 r = sd_bus_message_new_method_call(
214 bus,
215 &req,
216 "org.freedesktop.resolve1",
217 "/org/freedesktop/resolve1",
218 "org.freedesktop.resolve1.Manager",
219 "ResolveAddress");
220 if (r < 0)
221 return bus_log_create_error(r);
222
51323288 223 r = sd_bus_message_append(req, "ii", ifindex, family);
79266746
LP
224 if (r < 0)
225 return bus_log_create_error(r);
226
227 r = sd_bus_message_append_array(req, 'y', address, FAMILY_ADDRESS_SIZE(family));
228 if (r < 0)
229 return bus_log_create_error(r);
230
51323288 231 r = sd_bus_message_append(req, "t", arg_flags);
79266746
LP
232 if (r < 0)
233 return bus_log_create_error(r);
234
74998408
TG
235 ts = now(CLOCK_MONOTONIC);
236
79266746
LP
237 r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply);
238 if (r < 0) {
239 log_error("%s: resolve call failed: %s", pretty, bus_error_message(&error, r));
240 return r;
241 }
242
74998408
TG
243 ts = now(CLOCK_MONOTONIC) - ts;
244
78c6a153 245 r = sd_bus_message_enter_container(reply, 'a', "(is)");
79266746
LP
246 if (r < 0)
247 return bus_log_create_error(r);
248
78c6a153
LP
249 while ((r = sd_bus_message_enter_container(reply, 'r', "is")) > 0) {
250 const char *n;
251
252 assert_cc(sizeof(int) == sizeof(int32_t));
79266746 253
78c6a153
LP
254 r = sd_bus_message_read(reply, "is", &ifindex, &n);
255 if (r < 0)
256 return r;
257
258 r = sd_bus_message_exit_container(reply);
259 if (r < 0)
260 return r;
261
262 ifname[0] = 0;
263 if (ifindex > 0 && !if_indextoname(ifindex, ifname))
264 log_warning_errno(errno, "Failed to resolve interface name for index %i: %m", ifindex);
265
266 printf("%*s%*s%*s%s %s\n",
79266746 267 (int) strlen(pretty), c == 0 ? pretty : "",
78c6a153
LP
268 isempty(ifname) ? 0 : 1, c > 0 || isempty(ifname) ? "" : "%",
269 (int) strlen(ifname), c == 0 ? ifname : "",
79266746
LP
270 c == 0 ? ":" : " ",
271 n);
272
273 c++;
bdef7319 274 }
79266746
LP
275 if (r < 0)
276 return bus_log_parse_error(r);
bdef7319 277
02dd6e18
LP
278 r = sd_bus_message_exit_container(reply);
279 if (r < 0)
280 return bus_log_parse_error(r);
281
51323288
LP
282 r = sd_bus_message_read(reply, "t", &flags);
283 if (r < 0)
284 return bus_log_parse_error(r);
285
79266746
LP
286 if (c == 0) {
287 log_error("%s: no names found", pretty);
288 return -ESRCH;
289 }
290
78c6a153 291 print_source(flags, ts);
51323288 292
79266746
LP
293 return 0;
294}
295
296static int parse_address(const char *s, int *family, union in_addr_union *address, int *ifindex) {
297 const char *percent, *a;
298 int ifi = 0;
299 int r;
300
301 percent = strchr(s, '%');
302 if (percent) {
303 r = safe_atoi(percent+1, &ifi);
304 if (r < 0 || ifi <= 0) {
305 ifi = if_nametoindex(percent+1);
306 if (ifi <= 0)
307 return -EINVAL;
308 }
309
310 a = strndupa(s, percent - s);
311 } else
312 a = s;
313
314 r = in_addr_from_string_auto(a, family, address);
315 if (r < 0)
316 return r;
317
318 *ifindex = ifi;
02dd6e18 319 return 0;
bdef7319
ZJS
320}
321
2d4c5cbc
LP
322static int resolve_record(sd_bus *bus, const char *name) {
323
324 _cleanup_bus_message_unref_ sd_bus_message *req = NULL, *reply = NULL;
325 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
78c6a153 326 char ifname[IF_NAMESIZE] = "";
2d4c5cbc 327 unsigned n = 0;
51323288 328 uint64_t flags;
78c6a153 329 int r;
74998408 330 usec_t ts;
2d4c5cbc
LP
331
332 assert(name);
333
78c6a153
LP
334 if (arg_ifindex > 0 && !if_indextoname(arg_ifindex, ifname))
335 return log_error_errno(errno, "Failed to resolve interface name for index %i: %m", arg_ifindex);
336
337 log_debug("Resolving %s %s %s (interface %s).", name, dns_class_to_string(arg_class), dns_type_to_string(arg_type), isempty(ifname) ? "*" : ifname);
2d4c5cbc
LP
338
339 r = sd_bus_message_new_method_call(
340 bus,
341 &req,
342 "org.freedesktop.resolve1",
343 "/org/freedesktop/resolve1",
344 "org.freedesktop.resolve1.Manager",
345 "ResolveRecord");
346 if (r < 0)
347 return bus_log_create_error(r);
348
de292aa1 349 assert((uint16_t) arg_type == arg_type);
51323288 350 r = sd_bus_message_append(req, "isqqt", arg_ifindex, name, arg_class, arg_type, arg_flags);
2d4c5cbc
LP
351 if (r < 0)
352 return bus_log_create_error(r);
353
74998408
TG
354 ts = now(CLOCK_MONOTONIC);
355
2d4c5cbc
LP
356 r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply);
357 if (r < 0) {
358 log_error("%s: resolve call failed: %s", name, bus_error_message(&error, r));
359 return r;
360 }
361
74998408
TG
362 ts = now(CLOCK_MONOTONIC) - ts;
363
78c6a153 364 r = sd_bus_message_enter_container(reply, 'a', "(iqqay)");
51323288
LP
365 if (r < 0)
366 return bus_log_parse_error(r);
367
78c6a153 368 while ((r = sd_bus_message_enter_container(reply, 'r', "iqqay")) > 0) {
2d4c5cbc
LP
369 _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
370 _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
371 _cleanup_free_ char *s = NULL;
372 uint16_t c, t;
78c6a153 373 int ifindex;
2d4c5cbc
LP
374 const void *d;
375 size_t l;
376
78c6a153
LP
377 assert_cc(sizeof(int) == sizeof(int32_t));
378
379 r = sd_bus_message_read(reply, "iqq", &ifindex, &c, &t);
2d4c5cbc
LP
380 if (r < 0)
381 return bus_log_parse_error(r);
382
383 r = sd_bus_message_read_array(reply, 'y', &d, &l);
384 if (r < 0)
385 return bus_log_parse_error(r);
386
387 r = sd_bus_message_exit_container(reply);
388 if (r < 0)
389 return bus_log_parse_error(r);
390
391 r = dns_packet_new(&p, DNS_PROTOCOL_DNS, 0);
392 if (r < 0)
393 return log_oom();
394
f6a5fec6
LP
395 p->refuse_compression = true;
396
2d4c5cbc
LP
397 r = dns_packet_append_blob(p, d, l, NULL);
398 if (r < 0)
399 return log_oom();
400
401 r = dns_packet_read_rr(p, &rr, NULL);
402 if (r < 0) {
403 log_error("Failed to parse RR.");
404 return r;
405 }
406
407 r = dns_resource_record_to_string(rr, &s);
408 if (r < 0) {
409 log_error("Failed to format RR.");
410 return r;
411 }
412
78c6a153
LP
413 ifname[0] = 0;
414 if (ifindex > 0 && !if_indextoname(ifindex, ifname))
415 log_warning_errno(errno, "Failed to resolve interface name for index %i: %m", ifindex);
416
417 printf("%s%s%s\n", s, isempty(ifname) ? "" : " # interface ", ifname);
2d4c5cbc
LP
418 n++;
419 }
420 if (r < 0)
421 return bus_log_parse_error(r);
422
423 r = sd_bus_message_exit_container(reply);
424 if (r < 0)
425 return bus_log_parse_error(r);
426
51323288
LP
427 r = sd_bus_message_read(reply, "t", &flags);
428 if (r < 0)
429 return bus_log_parse_error(r);
430
2d4c5cbc
LP
431 if (n == 0) {
432 log_error("%s: no records found", name);
433 return -ESRCH;
434 }
435
78c6a153 436 print_source(flags, ts);
51323288 437
2d4c5cbc
LP
438 return 0;
439}
440
b93312f5
ZJS
441static void help_dns_types(void) {
442 int i;
443 const char *t;
444
445 if (arg_legend)
446 puts("Known dns types:");
447 for (i = 0; i < _DNS_TYPE_MAX; i++) {
448 t = dns_type_to_string(i);
449 if (t)
450 puts(t);
451 }
452}
453
454static void help_dns_classes(void) {
455 int i;
456 const char *t;
457
458 if (arg_legend)
459 puts("Known dns classes:");
460 for (i = 0; i < _DNS_CLASS_MAX; i++) {
461 t = dns_class_to_string(i);
462 if (t)
463 puts(t);
464 }
465}
466
bdef7319
ZJS
467static void help(void) {
468 printf("%s [OPTIONS...]\n\n"
469 "Resolve IPv4 or IPv6 addresses.\n\n"
51323288
LP
470 " -h --help Show this help\n"
471 " --version Show package version\n"
472 " -4 Resolve IPv4 addresses\n"
473 " -6 Resolve IPv6 addresses\n"
474 " -i INTERFACE Look on interface\n"
475 " -p --protocol=PROTOCOL Look via protocol\n"
476 " -t --type=TYPE Query RR with DNS type\n"
477 " -c --class=CLASS Query RR with DNS class\n"
dad29dff 478 " --legend[=BOOL] Do [not] print column headers\n"
79266746 479 , program_invocation_short_name);
bdef7319
ZJS
480}
481
482static int parse_argv(int argc, char *argv[]) {
483 enum {
484 ARG_VERSION = 0x100,
dad29dff 485 ARG_LEGEND,
bdef7319
ZJS
486 };
487
488 static const struct option options[] = {
51323288
LP
489 { "help", no_argument, NULL, 'h' },
490 { "version", no_argument, NULL, ARG_VERSION },
491 { "type", required_argument, NULL, 't' },
492 { "class", required_argument, NULL, 'c' },
dad29dff 493 { "legend", optional_argument, NULL, ARG_LEGEND },
51323288 494 { "protocol", required_argument, NULL, 'p' },
bdef7319
ZJS
495 {}
496 };
497
2d4c5cbc 498 int c, r;
bdef7319
ZJS
499
500 assert(argc >= 0);
501 assert(argv);
502
51323288 503 while ((c = getopt_long(argc, argv, "h46i:t:c:p:", options, NULL)) >= 0)
bdef7319
ZJS
504 switch(c) {
505
506 case 'h':
507 help();
508 return 0; /* done */;
509
510 case ARG_VERSION:
511 puts(PACKAGE_STRING);
512 puts(SYSTEMD_FEATURES);
513 return 0 /* done */;
514
515 case '4':
516 arg_family = AF_INET;
517 break;
518
519 case '6':
520 arg_family = AF_INET6;
521 break;
522
523 case 'i':
524 arg_ifindex = if_nametoindex(optarg);
4a62c710
MS
525 if (arg_ifindex <= 0)
526 return log_error_errno(errno, "Unknown interfaces %s: %m", optarg);
2d4c5cbc
LP
527 break;
528
529 case 't':
b93312f5
ZJS
530 if (streq(optarg, "help")) {
531 help_dns_types();
532 return 0;
533 }
534
de292aa1
ZJS
535 arg_type = dns_type_from_string(optarg);
536 if (arg_type < 0) {
2d4c5cbc 537 log_error("Failed to parse RR record type %s", optarg);
590baf91 538 return arg_type;
2d4c5cbc 539 }
de292aa1 540 assert(arg_type > 0 && (uint16_t) arg_type == arg_type);
b93312f5 541
2d4c5cbc
LP
542 break;
543
544 case 'c':
b93312f5
ZJS
545 if (streq(optarg, "help")) {
546 help_dns_classes();
547 return 0;
548 }
549
2d4c5cbc
LP
550 r = dns_class_from_string(optarg, &arg_class);
551 if (r < 0) {
552 log_error("Failed to parse RR record class %s", optarg);
553 return r;
bdef7319 554 }
b93312f5
ZJS
555
556 break;
557
dad29dff
LP
558 case ARG_LEGEND:
559 if (optarg) {
560 r = parse_boolean(optarg);
561 if (r < 0) {
562 log_error("Failed to parse --legend= argument");
563 return r;
564 }
565
566 arg_legend = !!r;
567 } else
568 arg_legend = false;
bdef7319
ZJS
569 break;
570
51323288
LP
571 case 'p':
572 if (streq(optarg, "dns"))
573 arg_flags |= SD_RESOLVED_DNS;
574 else if (streq(optarg, "llmnr"))
575 arg_flags |= SD_RESOLVED_LLMNR;
576 else if (streq(optarg, "llmnr-ipv4"))
577 arg_flags |= SD_RESOLVED_LLMNR_IPV4;
578 else if (streq(optarg, "llmnr-ipv6"))
579 arg_flags |= SD_RESOLVED_LLMNR_IPV6;
580 else {
581 log_error("Unknown protocol specifier: %s", optarg);
582 return -EINVAL;
583 }
584
585 break;
586
bdef7319
ZJS
587 case '?':
588 return -EINVAL;
589
590 default:
591 assert_not_reached("Unhandled option");
592 }
593
2d4c5cbc
LP
594 if (arg_type == 0 && arg_class != 0) {
595 log_error("--class= may only be used in conjunction with --type=");
596 return -EINVAL;
597 }
598
599 if (arg_type != 0 && arg_class == 0)
600 arg_class = DNS_CLASS_IN;
601
bdef7319
ZJS
602 return 1 /* work to do */;
603}
604
bdef7319 605int main(int argc, char **argv) {
03976f7b 606 _cleanup_bus_flush_close_unref_ sd_bus *bus = NULL;
bdef7319
ZJS
607 int r;
608
609 log_parse_environment();
610 log_open();
611
612 r = parse_argv(argc, argv);
613 if (r <= 0)
79266746
LP
614 goto finish;
615
616 if (optind >= argc) {
617 log_error("No arguments passed");
618 r = -EINVAL;
619 goto finish;
620 }
bdef7319
ZJS
621
622 r = sd_bus_open_system(&bus);
623 if (r < 0) {
da927ba9 624 log_error_errno(r, "sd_bus_open_system: %m");
79266746 625 goto finish;
bdef7319
ZJS
626 }
627
628 while (argv[optind]) {
79266746
LP
629 int family, ifindex, k;
630 union in_addr_union a;
631
2d4c5cbc
LP
632 if (arg_type != 0)
633 k = resolve_record(bus, argv[optind]);
634 else {
635 k = parse_address(argv[optind], &family, &a, &ifindex);
636 if (k >= 0)
637 k = resolve_address(bus, family, &a, ifindex);
638 else
639 k = resolve_host(bus, argv[optind]);
640 }
bdef7319 641
bdef7319
ZJS
642 if (r == 0)
643 r = k;
79266746
LP
644
645 optind++;
bdef7319
ZJS
646 }
647
79266746 648finish:
bdef7319
ZJS
649 return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
650}