chiark / gitweb /
Revert "journalctl: remove unexpected behavior of journalctl -b"
[elogind.git] / src / locale / localectl.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2012 Lennart Poettering
7   Copyright 2013 Kay Sievers
8
9   systemd is free software; you can redistribute it and/or modify it
10   under the terms of the GNU Lesser General Public License as published by
11   the Free Software Foundation; either version 2.1 of the License, or
12   (at your option) any later version.
13
14   systemd is distributed in the hope that it will be useful, but
15   WITHOUT ANY WARRANTY; without even the implied warranty of
16   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17   Lesser General Public License for more details.
18
19   You should have received a copy of the GNU Lesser General Public License
20   along with systemd; If not, see <http://www.gnu.org/licenses/>.
21 ***/
22
23 #include <locale.h>
24 #include <stdlib.h>
25 #include <stdbool.h>
26 #include <unistd.h>
27 #include <getopt.h>
28 #include <string.h>
29 #include <ftw.h>
30 #include <sys/mman.h>
31 #include <fcntl.h>
32
33 #include "sd-bus.h"
34 #include "bus-util.h"
35 #include "bus-error.h"
36 #include "bus-message.h"
37 #include "util.h"
38 #include "spawn-polkit-agent.h"
39 #include "build.h"
40 #include "strv.h"
41 #include "pager.h"
42 #include "set.h"
43 #include "path-util.h"
44 #include "utf8.h"
45 #include "def.h"
46
47 static bool arg_no_pager = false;
48 static bool arg_ask_password = true;
49 static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
50 static char *arg_host = NULL;
51 static bool arg_convert = true;
52
53 static void pager_open_if_enabled(void) {
54
55         if (arg_no_pager)
56                 return;
57
58         pager_open(false);
59 }
60
61 static void polkit_agent_open_if_enabled(void) {
62
63         /* Open the polkit agent as a child process if necessary */
64         if (!arg_ask_password)
65                 return;
66
67         if (arg_transport != BUS_TRANSPORT_LOCAL)
68                 return;
69
70         polkit_agent_open();
71 }
72
73 typedef struct StatusInfo {
74         char **locale;
75         const char *vconsole_keymap;
76         const char *vconsole_keymap_toggle;
77         const char *x11_layout;
78         const char *x11_model;
79         const char *x11_variant;
80         const char *x11_options;
81 } StatusInfo;
82
83 static void print_status_info(StatusInfo *i) {
84         assert(i);
85
86         if (strv_isempty(i->locale))
87                 puts("   System Locale: n/a\n");
88         else {
89                 char **j;
90
91                 printf("   System Locale: %s\n", i->locale[0]);
92                 STRV_FOREACH(j, i->locale + 1)
93                         printf("                  %s\n", *j);
94         }
95
96         printf("       VC Keymap: %s\n", strna(i->vconsole_keymap));
97         if (!isempty(i->vconsole_keymap_toggle))
98                 printf("VC Toggle Keymap: %s\n", i->vconsole_keymap_toggle);
99
100         printf("      X11 Layout: %s\n", strna(i->x11_layout));
101         if (!isempty(i->x11_model))
102                 printf("       X11 Model: %s\n", i->x11_model);
103         if (!isempty(i->x11_variant))
104                 printf("     X11 Variant: %s\n", i->x11_variant);
105         if (!isempty(i->x11_options))
106                 printf("     X11 Options: %s\n", i->x11_options);
107 }
108
109 static int show_status(sd_bus *bus, char **args, unsigned n) {
110         StatusInfo info = {};
111         static const struct bus_properties_map map[]  = {
112                 { "VConsoleKeymap",       "s",  NULL, offsetof(StatusInfo, vconsole_keymap) },
113                 { "VConsoleKeymap",       "s",  NULL, offsetof(StatusInfo, vconsole_keymap) },
114                 { "VConsoleKeymapToggle", "s",  NULL, offsetof(StatusInfo, vconsole_keymap_toggle) },
115                 { "X11Layout",            "s",  NULL, offsetof(StatusInfo, x11_layout) },
116                 { "X11Model",             "s",  NULL, offsetof(StatusInfo, x11_model) },
117                 { "X11Variant",           "s",  NULL, offsetof(StatusInfo, x11_variant) },
118                 { "X11Options",           "s",  NULL, offsetof(StatusInfo, x11_options) },
119                 { "Locale",               "as", NULL, offsetof(StatusInfo, locale) },
120                 {}
121         };
122         int r;
123
124         assert(bus);
125
126         r = bus_map_all_properties(bus,
127                                    "org.freedesktop.locale1",
128                                    "/org/freedesktop/locale1",
129                                    map,
130                                    &info);
131         if (r < 0)
132                 goto fail;
133
134         print_status_info(&info);
135
136 fail:
137         strv_free(info.locale);
138         return r;
139 }
140
141 static int set_locale(sd_bus *bus, char **args, unsigned n) {
142         _cleanup_bus_message_unref_ sd_bus_message *m = NULL;
143         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
144         int r;
145
146         assert(bus);
147         assert(args);
148
149         polkit_agent_open_if_enabled();
150
151         r = sd_bus_message_new_method_call(bus,
152                         "org.freedesktop.locale1",
153                         "/org/freedesktop/locale1",
154                         "org.freedesktop.locale1",
155                         "SetLocale", &m);
156         if (r < 0)
157                 return bus_log_create_error(r);
158
159         r = sd_bus_message_append_strv(m, args + 1);
160         if (r < 0)
161                 return bus_log_create_error(r);
162
163         r = sd_bus_message_append(m, "b", arg_ask_password);
164         if (r < 0)
165                 return bus_log_create_error(r);
166
167         r = sd_bus_call(bus, m, 0, &error, NULL);
168         if (r < 0) {
169                 log_error("Failed to issue method call: %s", bus_error_message(&error, -r));
170                 return r;
171         }
172
173         return 0;
174 }
175
176 static int add_locales_from_archive(Set *locales) {
177         /* Stolen from glibc... */
178
179         struct locarhead {
180                 uint32_t magic;
181                 /* Serial number.  */
182                 uint32_t serial;
183                 /* Name hash table.  */
184                 uint32_t namehash_offset;
185                 uint32_t namehash_used;
186                 uint32_t namehash_size;
187                 /* String table.  */
188                 uint32_t string_offset;
189                 uint32_t string_used;
190                 uint32_t string_size;
191                 /* Table with locale records.  */
192                 uint32_t locrectab_offset;
193                 uint32_t locrectab_used;
194                 uint32_t locrectab_size;
195                 /* MD5 sum hash table.  */
196                 uint32_t sumhash_offset;
197                 uint32_t sumhash_used;
198                 uint32_t sumhash_size;
199         };
200
201         struct namehashent {
202                 /* Hash value of the name.  */
203                 uint32_t hashval;
204                 /* Offset of the name in the string table.  */
205                 uint32_t name_offset;
206                 /* Offset of the locale record.  */
207                 uint32_t locrec_offset;
208         };
209
210         const struct locarhead *h;
211         const struct namehashent *e;
212         const void *p = MAP_FAILED;
213         _cleanup_close_ int fd = -1;
214         size_t sz = 0;
215         struct stat st;
216         unsigned i;
217         int r;
218
219         fd = open("/usr/lib/locale/locale-archive", O_RDONLY|O_NOCTTY|O_CLOEXEC);
220         if (fd < 0) {
221                 if (errno != ENOENT)
222                         log_error("Failed to open locale archive: %m");
223                 r = -errno;
224                 goto finish;
225         }
226
227         if (fstat(fd, &st) < 0) {
228                 log_error("fstat() failed: %m");
229                 r = -errno;
230                 goto finish;
231         }
232
233         if (!S_ISREG(st.st_mode)) {
234                 log_error("Archive file is not regular");
235                 r = -EBADMSG;
236                 goto finish;
237         }
238
239         if (st.st_size < (off_t) sizeof(struct locarhead)) {
240                 log_error("Archive has invalid size");
241                 r = -EBADMSG;
242                 goto finish;
243         }
244
245         p = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
246         if (p == MAP_FAILED) {
247                 log_error("Failed to map archive: %m");
248                 r = -errno;
249                 goto finish;
250         }
251
252         h = (const struct locarhead *) p;
253         if (h->magic != 0xde020109 ||
254             h->namehash_offset + h->namehash_size > st.st_size ||
255             h->string_offset + h->string_size > st.st_size ||
256             h->locrectab_offset + h->locrectab_size > st.st_size ||
257             h->sumhash_offset + h->sumhash_size > st.st_size) {
258                 log_error("Invalid archive file.");
259                 r = -EBADMSG;
260                 goto finish;
261         }
262
263         e = (const struct namehashent*) ((const uint8_t*) p + h->namehash_offset);
264         for (i = 0; i < h->namehash_size; i++) {
265                 char *z;
266
267                 if (e[i].locrec_offset == 0)
268                         continue;
269
270                 if (!utf8_is_valid((char*) p + e[i].name_offset))
271                         continue;
272
273                 z = strdup((char*) p + e[i].name_offset);
274                 if (!z) {
275                         r = log_oom();
276                         goto finish;
277                 }
278
279                 r = set_consume(locales, z);
280                 if (r < 0) {
281                         log_error("Failed to add locale: %s", strerror(-r));
282                         goto finish;
283                 }
284         }
285
286         r = 0;
287
288  finish:
289         if (p != MAP_FAILED)
290                 munmap((void*) p, sz);
291
292         return r;
293 }
294
295 static int add_locales_from_libdir (Set *locales) {
296         _cleanup_closedir_ DIR *dir;
297         struct dirent *entry;
298         int r;
299
300         dir = opendir("/usr/lib/locale");
301         if (!dir) {
302                 log_error("Failed to open locale directory: %m");
303                 return -errno;
304         }
305
306         errno = 0;
307         while ((entry = readdir(dir))) {
308                 char *z;
309
310                 if (entry->d_type != DT_DIR)
311                         continue;
312
313                 if (ignore_file(entry->d_name))
314                         continue;
315
316                 z = strdup(entry->d_name);
317                 if (!z)
318                         return log_oom();
319
320                 r = set_consume(locales, z);
321                 if (r < 0 && r != -EEXIST) {
322                         log_error("Failed to add locale: %s", strerror(-r));
323                         return r;
324                 }
325
326                 errno = 0;
327         }
328
329         if (errno > 0) {
330                 log_error("Failed to read locale directory: %m");
331                 return -errno;
332         }
333
334         return 0;
335 }
336
337 static int list_locales(sd_bus *bus, char **args, unsigned n) {
338         _cleanup_set_free_ Set *locales;
339         _cleanup_strv_free_ char **l = NULL;
340         int r;
341
342         locales = set_new(string_hash_func, string_compare_func);
343         if (!locales)
344                 return log_oom();
345
346         r = add_locales_from_archive(locales);
347         if (r < 0 && r != -ENOENT)
348                 return r;
349
350         r = add_locales_from_libdir(locales);
351         if (r < 0)
352                 return r;
353
354         l = set_get_strv(locales);
355         if (!l)
356                 return log_oom();
357
358         strv_sort(l);
359
360         pager_open_if_enabled();
361
362         strv_print(l);
363
364         return 0;
365 }
366
367 static int set_vconsole_keymap(sd_bus *bus, char **args, unsigned n) {
368         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
369         const char *map, *toggle_map;
370         int r;
371
372         assert(bus);
373         assert(args);
374
375         if (n > 3) {
376                 log_error("Too many arguments.");
377                 return -EINVAL;
378         }
379
380         polkit_agent_open_if_enabled();
381
382         map = args[1];
383         toggle_map = n > 2 ? args[2] : "";
384
385         r = sd_bus_call_method(
386                         bus,
387                         "org.freedesktop.locale1",
388                         "/org/freedesktop/locale1",
389                         "org.freedesktop.locale1",
390                         "SetVConsoleKeyboard",
391                         &error,
392                         NULL,
393                         "ssbb", map, toggle_map, arg_convert, arg_ask_password);
394         if (r < 0)
395                 log_error("Failed to set keymap: %s", bus_error_message(&error, -r));
396
397         return r;
398 }
399
400 static Set *keymaps = NULL;
401
402 static int nftw_cb(
403                 const char *fpath,
404                 const struct stat *sb,
405                 int tflag,
406                 struct FTW *ftwbuf) {
407
408         char *p, *e;
409         int r;
410
411         if (tflag != FTW_F)
412                 return 0;
413
414         if (!endswith(fpath, ".map") &&
415             !endswith(fpath, ".map.gz"))
416                 return 0;
417
418         p = strdup(basename(fpath));
419         if (!p)
420                 return log_oom();
421
422         e = endswith(p, ".map");
423         if (e)
424                 *e = 0;
425
426         e = endswith(p, ".map.gz");
427         if (e)
428                 *e = 0;
429
430         r = set_consume(keymaps, p);
431         if (r < 0 && r != -EEXIST) {
432                 log_error("Can't add keymap: %s", strerror(-r));
433                 return r;
434         }
435
436         return 0;
437 }
438
439 static int list_vconsole_keymaps(sd_bus *bus, char **args, unsigned n) {
440         _cleanup_strv_free_ char **l = NULL;
441         const char *dir;
442
443         keymaps = set_new(string_hash_func, string_compare_func);
444         if (!keymaps)
445                 return log_oom();
446
447         NULSTR_FOREACH(dir, KBD_KEYMAP_DIRS)
448                 nftw(dir, nftw_cb, 20, FTW_MOUNT|FTW_PHYS);
449
450         l = set_get_strv(keymaps);
451         if (!l) {
452                 set_free_free(keymaps);
453                 return log_oom();
454         }
455
456         set_free(keymaps);
457
458         if (strv_isempty(l)) {
459                 log_error("Couldn't find any console keymaps.");
460                 return -ENOENT;
461         }
462
463         strv_sort(l);
464
465         pager_open_if_enabled();
466
467         strv_print(l);
468
469         return 0;
470 }
471
472 static int set_x11_keymap(sd_bus *bus, char **args, unsigned n) {
473         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
474         const char *layout, *model, *variant, *options;
475         int r;
476
477         assert(bus);
478         assert(args);
479
480         if (n > 5) {
481                 log_error("Too many arguments.");
482                 return -EINVAL;
483         }
484
485         polkit_agent_open_if_enabled();
486
487         layout = args[1];
488         model = n > 2 ? args[2] : "";
489         variant = n > 3 ? args[3] : "";
490         options = n > 4 ? args[4] : "";
491
492         r = sd_bus_call_method(
493                         bus,
494                         "org.freedesktop.locale1",
495                         "/org/freedesktop/locale1",
496                         "org.freedesktop.locale1",
497                         "SetX11Keyboard",
498                         &error,
499                         NULL,
500                         "ssssbb", layout, model, variant, options,
501                                   arg_convert, arg_ask_password);
502         if (r < 0)
503                 log_error("Failed to set keymap: %s", bus_error_message(&error, -r));
504
505         return r;
506 }
507
508 static int list_x11_keymaps(sd_bus *bus, char **args, unsigned n) {
509         _cleanup_fclose_ FILE *f = NULL;
510         _cleanup_strv_free_ char **list = NULL;
511         char line[LINE_MAX];
512         enum {
513                 NONE,
514                 MODELS,
515                 LAYOUTS,
516                 VARIANTS,
517                 OPTIONS
518         } state = NONE, look_for;
519         int r;
520
521         if (n > 2) {
522                 log_error("Too many arguments.");
523                 return -EINVAL;
524         }
525
526         f = fopen("/usr/share/X11/xkb/rules/base.lst", "re");
527         if (!f) {
528                 log_error("Failed to open keyboard mapping list. %m");
529                 return -errno;
530         }
531
532         if (streq(args[0], "list-x11-keymap-models"))
533                 look_for = MODELS;
534         else if (streq(args[0], "list-x11-keymap-layouts"))
535                 look_for = LAYOUTS;
536         else if (streq(args[0], "list-x11-keymap-variants"))
537                 look_for = VARIANTS;
538         else if (streq(args[0], "list-x11-keymap-options"))
539                 look_for = OPTIONS;
540         else
541                 assert_not_reached("Wrong parameter");
542
543         FOREACH_LINE(line, f, break) {
544                 char *l, *w;
545
546                 l = strstrip(line);
547
548                 if (isempty(l))
549                         continue;
550
551                 if (l[0] == '!') {
552                         if (startswith(l, "! model"))
553                                 state = MODELS;
554                         else if (startswith(l, "! layout"))
555                                 state = LAYOUTS;
556                         else if (startswith(l, "! variant"))
557                                 state = VARIANTS;
558                         else if (startswith(l, "! option"))
559                                 state = OPTIONS;
560                         else
561                                 state = NONE;
562
563                         continue;
564                 }
565
566                 if (state != look_for)
567                         continue;
568
569                 w = l + strcspn(l, WHITESPACE);
570
571                 if (n > 1) {
572                         char *e;
573
574                         if (*w == 0)
575                                 continue;
576
577                         *w = 0;
578                         w++;
579                         w += strspn(w, WHITESPACE);
580
581                         e = strchr(w, ':');
582                         if (!e)
583                                 continue;
584
585                         *e = 0;
586
587                         if (!streq(w, args[1]))
588                                 continue;
589                 } else
590                         *w = 0;
591
592                  r = strv_extend(&list, l);
593                  if (r < 0)
594                          return log_oom();
595         }
596
597         if (strv_isempty(list)) {
598                 log_error("Couldn't find any entries.");
599                 return -ENOENT;
600         }
601
602         strv_sort(list);
603         strv_uniq(list);
604
605         pager_open_if_enabled();
606
607         strv_print(list);
608         return 0;
609 }
610
611 static int help(void) {
612
613         printf("%s [OPTIONS...] COMMAND ...\n\n"
614                "Query or change system locale and keyboard settings.\n\n"
615                "  -h --help                Show this help\n"
616                "     --version             Show package version\n"
617                "     --no-pager            Do not pipe output into a pager\n"
618                "     --no-ask-password     Do not prompt for password\n"
619                "  -H --host=[USER@]HOST    Operate on remote host\n"
620                "  -M --machine=CONTAINER   Operate on local container\n"
621                "     --no-convert          Don't convert keyboard mappings\n\n"
622                "Commands:\n"
623                "  status                   Show current locale settings\n"
624                "  set-locale LOCALE...     Set system locale\n"
625                "  list-locales             Show known locales\n"
626                "  set-keymap MAP [MAP]     Set virtual console keyboard mapping\n"
627                "  list-keymaps             Show known virtual console keyboard mappings\n"
628                "  set-x11-keymap LAYOUT [MODEL] [VARIANT] [OPTIONS]\n"
629                "                           Set X11 keyboard mapping\n"
630                "  list-x11-keymap-models   Show known X11 keyboard mapping models\n"
631                "  list-x11-keymap-layouts  Show known X11 keyboard mapping layouts\n"
632                "  list-x11-keymap-variants [LAYOUT]\n"
633                "                           Show known X11 keyboard mapping variants\n"
634                "  list-x11-keymap-options  Show known X11 keyboard mapping options\n",
635                program_invocation_short_name);
636
637         return 0;
638 }
639
640 static int parse_argv(int argc, char *argv[]) {
641
642         enum {
643                 ARG_VERSION = 0x100,
644                 ARG_NO_PAGER,
645                 ARG_NO_CONVERT,
646                 ARG_NO_ASK_PASSWORD
647         };
648
649         static const struct option options[] = {
650                 { "help",            no_argument,       NULL, 'h'                 },
651                 { "version",         no_argument,       NULL, ARG_VERSION         },
652                 { "no-pager",        no_argument,       NULL, ARG_NO_PAGER        },
653                 { "host",            required_argument, NULL, 'H'                 },
654                 { "machine",         required_argument, NULL, 'M'                 },
655                 { "no-ask-password", no_argument,       NULL, ARG_NO_ASK_PASSWORD },
656                 { "no-convert",      no_argument,       NULL, ARG_NO_CONVERT      },
657                 {}
658         };
659
660         int c;
661
662         assert(argc >= 0);
663         assert(argv);
664
665         while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0) {
666
667                 switch (c) {
668
669                 case 'h':
670                         return help();
671
672                 case ARG_VERSION:
673                         puts(PACKAGE_STRING);
674                         puts(SYSTEMD_FEATURES);
675                         return 0;
676
677                 case ARG_NO_CONVERT:
678                         arg_convert = false;
679                         break;
680
681                 case ARG_NO_PAGER:
682                         arg_no_pager = true;
683                         break;
684
685                 case ARG_NO_ASK_PASSWORD:
686                         arg_ask_password = false;
687                         break;
688
689                 case 'H':
690                         arg_transport = BUS_TRANSPORT_REMOTE;
691                         arg_host = optarg;
692                         break;
693
694                 case 'M':
695                         arg_transport = BUS_TRANSPORT_CONTAINER;
696                         arg_host = optarg;
697                         break;
698
699                 case '?':
700                         return -EINVAL;
701
702                 default:
703                         assert_not_reached("Unhandled option");
704                 }
705         }
706
707         return 1;
708 }
709
710 static int localectl_main(sd_bus *bus, int argc, char *argv[]) {
711
712         static const struct {
713                 const char* verb;
714                 const enum {
715                         MORE,
716                         LESS,
717                         EQUAL
718                 } argc_cmp;
719                 const int argc;
720                 int (* const dispatch)(sd_bus *bus, char **args, unsigned n);
721         } verbs[] = {
722                 { "status",                   LESS,   1, show_status           },
723                 { "set-locale",               MORE,   2, set_locale            },
724                 { "list-locales",             EQUAL,  1, list_locales          },
725                 { "set-keymap",               MORE,   2, set_vconsole_keymap   },
726                 { "list-keymaps",             EQUAL,  1, list_vconsole_keymaps },
727                 { "set-x11-keymap",           MORE,   2, set_x11_keymap        },
728                 { "list-x11-keymap-models",   EQUAL,  1, list_x11_keymaps      },
729                 { "list-x11-keymap-layouts",  EQUAL,  1, list_x11_keymaps      },
730                 { "list-x11-keymap-variants", LESS,   2, list_x11_keymaps      },
731                 { "list-x11-keymap-options",  EQUAL,  1, list_x11_keymaps      },
732         };
733
734         int left;
735         unsigned i;
736
737         assert(argc >= 0);
738         assert(argv);
739
740         left = argc - optind;
741
742         if (left <= 0)
743                 /* Special rule: no arguments means "status" */
744                 i = 0;
745         else {
746                 if (streq(argv[optind], "help")) {
747                         help();
748                         return 0;
749                 }
750
751                 for (i = 0; i < ELEMENTSOF(verbs); i++)
752                         if (streq(argv[optind], verbs[i].verb))
753                                 break;
754
755                 if (i >= ELEMENTSOF(verbs)) {
756                         log_error("Unknown operation %s", argv[optind]);
757                         return -EINVAL;
758                 }
759         }
760
761         switch (verbs[i].argc_cmp) {
762
763         case EQUAL:
764                 if (left != verbs[i].argc) {
765                         log_error("Invalid number of arguments.");
766                         return -EINVAL;
767                 }
768
769                 break;
770
771         case MORE:
772                 if (left < verbs[i].argc) {
773                         log_error("Too few arguments.");
774                         return -EINVAL;
775                 }
776
777                 break;
778
779         case LESS:
780                 if (left > verbs[i].argc) {
781                         log_error("Too many arguments.");
782                         return -EINVAL;
783                 }
784
785                 break;
786
787         default:
788                 assert_not_reached("Unknown comparison operator.");
789         }
790
791         return verbs[i].dispatch(bus, argv + optind, left);
792 }
793
794 int main(int argc, char*argv[]) {
795         _cleanup_bus_unref_ sd_bus *bus = NULL;
796         int r;
797
798         setlocale(LC_ALL, "");
799         log_parse_environment();
800         log_open();
801
802         r = parse_argv(argc, argv);
803         if (r <= 0)
804                 goto finish;
805
806         r = bus_open_transport(arg_transport, arg_host, false, &bus);
807         if (r < 0) {
808                 log_error("Failed to create bus connection: %s", strerror(-r));
809                 goto finish;
810         }
811
812         r = localectl_main(bus, argc, argv);
813
814 finish:
815         pager_close();
816
817         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
818 }