]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/cryptsetup/cryptsetup.c
b6d965e24ffca41f6e0fe216dbb4b74ec3190578
[thirdparty/systemd.git] / src / cryptsetup / cryptsetup.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 This file is part of systemd.
4
5 Copyright 2010 Lennart Poettering
6
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19 ***/
20
21 #include <errno.h>
22 #include <mntent.h>
23 #include <string.h>
24 #include <sys/mman.h>
25
26 #include "sd-device.h"
27
28 #include "alloc-util.h"
29 #include "ask-password-api.h"
30 #include "crypt-util.h"
31 #include "device-util.h"
32 #include "escape.h"
33 #include "fileio.h"
34 #include "log.h"
35 #include "mount-util.h"
36 #include "parse-util.h"
37 #include "path-util.h"
38 #include "string-util.h"
39 #include "strv.h"
40 #include "util.h"
41
42 /* internal helper */
43 #define ANY_LUKS "LUKS"
44
45 static const char *arg_type = NULL; /* ANY_LUKS, CRYPT_LUKS1, CRYPT_LUKS2, CRYPT_TCRYPT or CRYPT_PLAIN */
46 static char *arg_cipher = NULL;
47 static unsigned arg_key_size = 0;
48 static int arg_key_slot = CRYPT_ANY_SLOT;
49 static unsigned arg_keyfile_size = 0;
50 static unsigned arg_keyfile_offset = 0;
51 static char *arg_hash = NULL;
52 static char *arg_header = NULL;
53 static unsigned arg_tries = 3;
54 static bool arg_readonly = false;
55 static bool arg_verify = false;
56 static bool arg_discards = false;
57 static bool arg_tcrypt_hidden = false;
58 static bool arg_tcrypt_system = false;
59 #ifdef CRYPT_TCRYPT_VERA_MODES
60 static bool arg_tcrypt_veracrypt = false;
61 #endif
62 static char **arg_tcrypt_keyfiles = NULL;
63 static uint64_t arg_offset = 0;
64 static uint64_t arg_skip = 0;
65 static usec_t arg_timeout = USEC_INFINITY;
66
67 /* Options Debian's crypttab knows we don't:
68
69 precheck=
70 check=
71 checkargs=
72 noearly=
73 loud=
74 keyscript=
75 */
76
77 static int parse_one_option(const char *option) {
78 const char *val;
79 int r;
80
81 assert(option);
82
83 /* Handled outside of this tool */
84 if (STR_IN_SET(option, "noauto", "auto", "nofail", "fail", "_netdev"))
85 return 0;
86
87 if ((val = startswith(option, "cipher="))) {
88 r = free_and_strdup(&arg_cipher, val);
89 if (r < 0)
90 return log_oom();
91
92 } else if ((val = startswith(option, "size="))) {
93
94 r = safe_atou(val, &arg_key_size);
95 if (r < 0) {
96 log_error_errno(r, "Failed to parse %s, ignoring: %m", option);
97 return 0;
98 }
99
100 if (arg_key_size % 8) {
101 log_error("size= not a multiple of 8, ignoring.");
102 return 0;
103 }
104
105 arg_key_size /= 8;
106
107 } else if ((val = startswith(option, "key-slot="))) {
108
109 arg_type = ANY_LUKS;
110 r = safe_atoi(val, &arg_key_slot);
111 if (r < 0) {
112 log_error_errno(r, "Failed to parse %s, ignoring: %m", option);
113 return 0;
114 }
115
116 } else if ((val = startswith(option, "tcrypt-keyfile="))) {
117
118 arg_type = CRYPT_TCRYPT;
119 if (path_is_absolute(val)) {
120 if (strv_extend(&arg_tcrypt_keyfiles, val) < 0)
121 return log_oom();
122 } else
123 log_error("Key file path \"%s\" is not absolute. Ignoring.", val);
124
125 } else if ((val = startswith(option, "keyfile-size="))) {
126
127 r = safe_atou(val, &arg_keyfile_size);
128 if (r < 0) {
129 log_error_errno(r, "Failed to parse %s, ignoring: %m", option);
130 return 0;
131 }
132
133 } else if ((val = startswith(option, "keyfile-offset="))) {
134
135 r = safe_atou(val, &arg_keyfile_offset);
136 if (r < 0) {
137 log_error_errno(r, "Failed to parse %s, ignoring: %m", option);
138 return 0;
139 }
140
141 } else if ((val = startswith(option, "hash="))) {
142 r = free_and_strdup(&arg_hash, val);
143 if (r < 0)
144 return log_oom();
145
146 } else if ((val = startswith(option, "header="))) {
147 arg_type = ANY_LUKS;
148
149 if (!path_is_absolute(val)) {
150 log_error("Header path \"%s\" is not absolute, refusing.", val);
151 return -EINVAL;
152 }
153
154 if (arg_header) {
155 log_error("Duplicate header= option, refusing.");
156 return -EINVAL;
157 }
158
159 arg_header = strdup(val);
160 if (!arg_header)
161 return log_oom();
162
163 } else if ((val = startswith(option, "tries="))) {
164
165 r = safe_atou(val, &arg_tries);
166 if (r < 0) {
167 log_error_errno(r, "Failed to parse %s, ignoring: %m", option);
168 return 0;
169 }
170
171 } else if (STR_IN_SET(option, "readonly", "read-only"))
172 arg_readonly = true;
173 else if (streq(option, "verify"))
174 arg_verify = true;
175 else if (STR_IN_SET(option, "allow-discards", "discard"))
176 arg_discards = true;
177 else if (streq(option, "luks"))
178 arg_type = ANY_LUKS;
179 else if (streq(option, "tcrypt"))
180 arg_type = CRYPT_TCRYPT;
181 else if (streq(option, "tcrypt-hidden")) {
182 arg_type = CRYPT_TCRYPT;
183 arg_tcrypt_hidden = true;
184 } else if (streq(option, "tcrypt-system")) {
185 arg_type = CRYPT_TCRYPT;
186 arg_tcrypt_system = true;
187 } else if (streq(option, "tcrypt-veracrypt")) {
188 #ifdef CRYPT_TCRYPT_VERA_MODES
189 arg_type = CRYPT_TCRYPT;
190 arg_tcrypt_veracrypt = true;
191 #else
192 log_error("This version of cryptsetup does not support tcrypt-veracrypt; refusing.");
193 return -EINVAL;
194 #endif
195 } else if (STR_IN_SET(option, "plain", "swap", "tmp"))
196 arg_type = CRYPT_PLAIN;
197 else if ((val = startswith(option, "timeout="))) {
198
199 r = parse_sec_fix_0(val, &arg_timeout);
200 if (r < 0) {
201 log_error_errno(r, "Failed to parse %s, ignoring: %m", option);
202 return 0;
203 }
204
205 } else if ((val = startswith(option, "offset="))) {
206
207 r = safe_atou64(val, &arg_offset);
208 if (r < 0)
209 return log_error_errno(r, "Failed to parse %s: %m", option);
210
211 } else if ((val = startswith(option, "skip="))) {
212
213 r = safe_atou64(val, &arg_skip);
214 if (r < 0)
215 return log_error_errno(r, "Failed to parse %s: %m", option);
216
217 } else if (!streq(option, "none"))
218 log_warning("Encountered unknown /etc/crypttab option '%s', ignoring.", option);
219
220 return 0;
221 }
222
223 static int parse_options(const char *options) {
224 const char *word, *state;
225 size_t l;
226 int r;
227
228 assert(options);
229
230 FOREACH_WORD_SEPARATOR(word, l, options, ",", state) {
231 _cleanup_free_ char *o;
232
233 o = strndup(word, l);
234 if (!o)
235 return -ENOMEM;
236 r = parse_one_option(o);
237 if (r < 0)
238 return r;
239 }
240
241 /* sanity-check options */
242 if (arg_type != NULL && !streq(arg_type, CRYPT_PLAIN)) {
243 if (arg_offset)
244 log_warning("offset= ignored with type %s", arg_type);
245 if (arg_skip)
246 log_warning("skip= ignored with type %s", arg_type);
247 }
248
249 return 0;
250 }
251
252 static int disk_major_minor(const char *path, char **ret) {
253 struct stat st;
254
255 assert(path);
256
257 if (stat(path, &st) < 0)
258 return -errno;
259
260 if (!S_ISBLK(st.st_mode))
261 return -EINVAL;
262
263 if (asprintf(ret, "/dev/block/%d:%d", major(st.st_rdev), minor(st.st_rdev)) < 0)
264 return -errno;
265
266 return 0;
267 }
268
269 static char* disk_description(const char *path) {
270
271 static const char name_fields[] =
272 "ID_PART_ENTRY_NAME\0"
273 "DM_NAME\0"
274 "ID_MODEL_FROM_DATABASE\0"
275 "ID_MODEL\0";
276
277 _cleanup_(sd_device_unrefp) sd_device *device = NULL;
278 struct stat st;
279 const char *i;
280 int r;
281
282 assert(path);
283
284 if (stat(path, &st) < 0)
285 return NULL;
286
287 if (!S_ISBLK(st.st_mode))
288 return NULL;
289
290 r = sd_device_new_from_devnum(&device, 'b', st.st_rdev);
291 if (r < 0)
292 return NULL;
293
294 NULSTR_FOREACH(i, name_fields) {
295 const char *name;
296
297 r = sd_device_get_property_value(device, i, &name);
298 if (r >= 0 && !isempty(name))
299 return strdup(name);
300 }
301
302 return NULL;
303 }
304
305 static char *disk_mount_point(const char *label) {
306 _cleanup_free_ char *device = NULL;
307 _cleanup_endmntent_ FILE *f = NULL;
308 struct mntent *m;
309
310 /* Yeah, we don't support native systemd unit files here for now */
311
312 if (asprintf(&device, "/dev/mapper/%s", label) < 0)
313 return NULL;
314
315 f = setmntent("/etc/fstab", "re");
316 if (!f)
317 return NULL;
318
319 while ((m = getmntent(f)))
320 if (path_equal(m->mnt_fsname, device))
321 return strdup(m->mnt_dir);
322
323 return NULL;
324 }
325
326 static int get_password(const char *vol, const char *src, usec_t until, bool accept_cached, char ***ret) {
327 _cleanup_free_ char *description = NULL, *name_buffer = NULL, *mount_point = NULL, *maj_min = NULL, *text = NULL, *escaped_name = NULL;
328 _cleanup_strv_free_erase_ char **passwords = NULL;
329 const char *name = NULL;
330 char **p, *id;
331 int r = 0;
332
333 assert(vol);
334 assert(src);
335 assert(ret);
336
337 description = disk_description(src);
338 mount_point = disk_mount_point(vol);
339
340 if (description && streq(vol, description))
341 /* If the description string is simply the
342 * volume name, then let's not show this
343 * twice */
344 description = mfree(description);
345
346 if (mount_point && description)
347 r = asprintf(&name_buffer, "%s (%s) on %s", description, vol, mount_point);
348 else if (mount_point)
349 r = asprintf(&name_buffer, "%s on %s", vol, mount_point);
350 else if (description)
351 r = asprintf(&name_buffer, "%s (%s)", description, vol);
352
353 if (r < 0)
354 return log_oom();
355
356 name = name_buffer ? name_buffer : vol;
357
358 if (asprintf(&text, "Please enter passphrase for disk %s!", name) < 0)
359 return log_oom();
360
361 if (src)
362 (void) disk_major_minor(src, &maj_min);
363
364 if (maj_min) {
365 escaped_name = maj_min;
366 maj_min = NULL;
367 } else
368 escaped_name = cescape(src);
369
370 if (!escaped_name)
371 return log_oom();
372
373 id = strjoina("cryptsetup:", escaped_name);
374
375 r = ask_password_auto(text, "drive-harddisk", id, "cryptsetup", until,
376 ASK_PASSWORD_PUSH_CACHE | (accept_cached*ASK_PASSWORD_ACCEPT_CACHED),
377 &passwords);
378 if (r < 0)
379 return log_error_errno(r, "Failed to query password: %m");
380
381 if (arg_verify) {
382 _cleanup_strv_free_erase_ char **passwords2 = NULL;
383
384 assert(strv_length(passwords) == 1);
385
386 if (asprintf(&text, "Please enter passphrase for disk %s! (verification)", name) < 0)
387 return log_oom();
388
389 id = strjoina("cryptsetup-verification:", escaped_name);
390
391 r = ask_password_auto(text, "drive-harddisk", id, "cryptsetup", until, ASK_PASSWORD_PUSH_CACHE, &passwords2);
392 if (r < 0)
393 return log_error_errno(r, "Failed to query verification password: %m");
394
395 assert(strv_length(passwords2) == 1);
396
397 if (!streq(passwords[0], passwords2[0])) {
398 log_warning("Passwords did not match, retrying.");
399 return -EAGAIN;
400 }
401 }
402
403 strv_uniq(passwords);
404
405 STRV_FOREACH(p, passwords) {
406 char *c;
407
408 if (strlen(*p)+1 >= arg_key_size)
409 continue;
410
411 /* Pad password if necessary */
412 c = new(char, arg_key_size);
413 if (!c)
414 return log_oom();
415
416 strncpy(c, *p, arg_key_size);
417 free(*p);
418 *p = c;
419 }
420
421 *ret = passwords;
422 passwords = NULL;
423
424 return 0;
425 }
426
427 static int attach_tcrypt(
428 struct crypt_device *cd,
429 const char *name,
430 const char *key_file,
431 char **passwords,
432 uint32_t flags) {
433
434 int r = 0;
435 _cleanup_free_ char *passphrase = NULL;
436 struct crypt_params_tcrypt params = {
437 .flags = CRYPT_TCRYPT_LEGACY_MODES,
438 .keyfiles = (const char **)arg_tcrypt_keyfiles,
439 .keyfiles_count = strv_length(arg_tcrypt_keyfiles)
440 };
441
442 assert(cd);
443 assert(name);
444 assert(key_file || (passwords && passwords[0]));
445
446 if (arg_tcrypt_hidden)
447 params.flags |= CRYPT_TCRYPT_HIDDEN_HEADER;
448
449 if (arg_tcrypt_system)
450 params.flags |= CRYPT_TCRYPT_SYSTEM_HEADER;
451
452 #ifdef CRYPT_TCRYPT_VERA_MODES
453 if (arg_tcrypt_veracrypt)
454 params.flags |= CRYPT_TCRYPT_VERA_MODES;
455 #endif
456
457 if (key_file) {
458 r = read_one_line_file(key_file, &passphrase);
459 if (r < 0) {
460 log_error_errno(r, "Failed to read password file '%s': %m", key_file);
461 return -EAGAIN;
462 }
463
464 params.passphrase = passphrase;
465 } else
466 params.passphrase = passwords[0];
467 params.passphrase_size = strlen(params.passphrase);
468
469 r = crypt_load(cd, CRYPT_TCRYPT, &params);
470 if (r < 0) {
471 if (key_file && r == -EPERM) {
472 log_error("Failed to activate using password file '%s'.", key_file);
473 return -EAGAIN;
474 }
475 return r;
476 }
477
478 return crypt_activate_by_volume_key(cd, name, NULL, 0, flags);
479 }
480
481 static int attach_luks_or_plain(struct crypt_device *cd,
482 const char *name,
483 const char *key_file,
484 const char *data_device,
485 char **passwords,
486 uint32_t flags) {
487 int r = 0;
488 bool pass_volume_key = false;
489
490 assert(cd);
491 assert(name);
492 assert(key_file || passwords);
493
494 if (!arg_type || STR_IN_SET(arg_type, ANY_LUKS, CRYPT_LUKS1)) {
495 r = crypt_load(cd, CRYPT_LUKS, NULL);
496 if (r < 0) {
497 log_error("crypt_load() failed on device %s.\n", crypt_get_device_name(cd));
498 return r;
499 }
500
501 if (data_device)
502 r = crypt_set_data_device(cd, data_device);
503 }
504
505 if ((!arg_type && r < 0) || streq_ptr(arg_type, CRYPT_PLAIN)) {
506 struct crypt_params_plain params = {
507 .offset = arg_offset,
508 .skip = arg_skip,
509 };
510 const char *cipher, *cipher_mode;
511 _cleanup_free_ char *truncated_cipher = NULL;
512
513 if (arg_hash) {
514 /* plain isn't a real hash type. it just means "use no hash" */
515 if (!streq(arg_hash, "plain"))
516 params.hash = arg_hash;
517 } else if (!key_file)
518 /* for CRYPT_PLAIN, the behaviour of cryptsetup
519 * package is to not hash when a key file is provided */
520 params.hash = "ripemd160";
521
522 if (arg_cipher) {
523 size_t l;
524
525 l = strcspn(arg_cipher, "-");
526 truncated_cipher = strndup(arg_cipher, l);
527 if (!truncated_cipher)
528 return log_oom();
529
530 cipher = truncated_cipher;
531 cipher_mode = arg_cipher[l] ? arg_cipher+l+1 : "plain";
532 } else {
533 cipher = "aes";
534 cipher_mode = "cbc-essiv:sha256";
535 }
536
537 /* for CRYPT_PLAIN limit reads
538 * from keyfile to key length, and
539 * ignore keyfile-size */
540 arg_keyfile_size = arg_key_size;
541
542 /* In contrast to what the name
543 * crypt_setup() might suggest this
544 * doesn't actually format anything,
545 * it just configures encryption
546 * parameters when used for plain
547 * mode. */
548 r = crypt_format(cd, CRYPT_PLAIN, cipher, cipher_mode, NULL, NULL, arg_keyfile_size, &params);
549
550 /* hash == NULL implies the user passed "plain" */
551 pass_volume_key = (params.hash == NULL);
552 }
553
554 if (r < 0)
555 return log_error_errno(r, "Loading of cryptographic parameters failed: %m");
556
557 log_info("Set cipher %s, mode %s, key size %i bits for device %s.",
558 crypt_get_cipher(cd),
559 crypt_get_cipher_mode(cd),
560 crypt_get_volume_key_size(cd)*8,
561 crypt_get_device_name(cd));
562
563 if (key_file) {
564 r = crypt_activate_by_keyfile_offset(cd, name, arg_key_slot, key_file, arg_keyfile_size, arg_keyfile_offset, flags);
565 if (r < 0) {
566 log_error_errno(r, "Failed to activate with key file '%s': %m", key_file);
567 return -EAGAIN;
568 }
569 } else {
570 char **p;
571
572 STRV_FOREACH(p, passwords) {
573 if (pass_volume_key)
574 r = crypt_activate_by_volume_key(cd, name, *p, arg_key_size, flags);
575 else
576 r = crypt_activate_by_passphrase(cd, name, arg_key_slot, *p, strlen(*p), flags);
577
578 if (r >= 0)
579 break;
580 }
581 }
582
583 return r;
584 }
585
586 static int help(void) {
587
588 printf("%s attach VOLUME SOURCEDEVICE [PASSWORD] [OPTIONS]\n"
589 "%s detach VOLUME\n\n"
590 "Attaches or detaches an encrypted block device.\n",
591 program_invocation_short_name,
592 program_invocation_short_name);
593
594 return 0;
595 }
596
597 int main(int argc, char *argv[]) {
598 _cleanup_(crypt_freep) struct crypt_device *cd = NULL;
599 int r = -EINVAL;
600
601 if (argc <= 1) {
602 r = help();
603 goto finish;
604 }
605
606 if (argc < 3) {
607 log_error("This program requires at least two arguments.");
608 goto finish;
609 }
610
611 log_set_target(LOG_TARGET_AUTO);
612 log_parse_environment();
613 log_open();
614
615 umask(0022);
616
617 if (streq(argv[1], "attach")) {
618 uint32_t flags = 0;
619 unsigned tries;
620 usec_t until;
621 crypt_status_info status;
622 const char *key_file = NULL;
623
624 /* Arguments: systemd-cryptsetup attach VOLUME SOURCE-DEVICE [PASSWORD] [OPTIONS] */
625
626 if (argc < 4) {
627 log_error("attach requires at least two arguments.");
628 goto finish;
629 }
630
631 if (argc >= 5 &&
632 argv[4][0] &&
633 !streq(argv[4], "-") &&
634 !streq(argv[4], "none")) {
635
636 if (!path_is_absolute(argv[4]))
637 log_error("Password file path '%s' is not absolute. Ignoring.", argv[4]);
638 else
639 key_file = argv[4];
640 }
641
642 if (argc >= 6 && argv[5][0] && !streq(argv[5], "-")) {
643 if (parse_options(argv[5]) < 0)
644 goto finish;
645 }
646
647 /* A delicious drop of snake oil */
648 mlockall(MCL_FUTURE);
649
650 if (arg_header) {
651 log_debug("LUKS header: %s", arg_header);
652 r = crypt_init(&cd, arg_header);
653 } else
654 r = crypt_init(&cd, argv[3]);
655 if (r < 0) {
656 log_error_errno(r, "crypt_init() failed: %m");
657 goto finish;
658 }
659
660 crypt_set_log_callback(cd, cryptsetup_log_glue, NULL);
661
662 status = crypt_status(cd, argv[2]);
663 if (IN_SET(status, CRYPT_ACTIVE, CRYPT_BUSY)) {
664 log_info("Volume %s already active.", argv[2]);
665 r = 0;
666 goto finish;
667 }
668
669 if (arg_readonly)
670 flags |= CRYPT_ACTIVATE_READONLY;
671
672 if (arg_discards)
673 flags |= CRYPT_ACTIVATE_ALLOW_DISCARDS;
674
675 if (arg_timeout == USEC_INFINITY)
676 until = 0;
677 else
678 until = now(CLOCK_MONOTONIC) + arg_timeout;
679
680 arg_key_size = (arg_key_size > 0 ? arg_key_size : (256 / 8));
681
682 if (key_file) {
683 struct stat st;
684
685 /* Ideally we'd do this on the open fd, but since this is just a
686 * warning it's OK to do this in two steps. */
687 if (stat(key_file, &st) >= 0 && S_ISREG(st.st_mode) && (st.st_mode & 0005))
688 log_warning("Key file %s is world-readable. This is not a good idea!", key_file);
689 }
690
691 for (tries = 0; arg_tries == 0 || tries < arg_tries; tries++) {
692 _cleanup_strv_free_erase_ char **passwords = NULL;
693
694 if (!key_file) {
695 r = get_password(argv[2], argv[3], until, tries == 0 && !arg_verify, &passwords);
696 if (r == -EAGAIN)
697 continue;
698 if (r < 0)
699 goto finish;
700 }
701
702 if (streq_ptr(arg_type, CRYPT_TCRYPT))
703 r = attach_tcrypt(cd, argv[2], key_file, passwords, flags);
704 else
705 r = attach_luks_or_plain(cd,
706 argv[2],
707 key_file,
708 arg_header ? argv[3] : NULL,
709 passwords,
710 flags);
711 if (r >= 0)
712 break;
713 if (r == -EAGAIN) {
714 key_file = NULL;
715 continue;
716 }
717 if (r != -EPERM) {
718 log_error_errno(r, "Failed to activate: %m");
719 goto finish;
720 }
721
722 log_warning("Invalid passphrase.");
723 }
724
725 if (arg_tries != 0 && tries >= arg_tries) {
726 log_error("Too many attempts; giving up.");
727 r = -EPERM;
728 goto finish;
729 }
730
731 } else if (streq(argv[1], "detach")) {
732
733 r = crypt_init_by_name(&cd, argv[2]);
734 if (r == -ENODEV) {
735 log_info("Volume %s already inactive.", argv[2]);
736 r = 0;
737 goto finish;
738 }
739 if (r < 0) {
740 log_error_errno(r, "crypt_init_by_name() failed: %m");
741 goto finish;
742 }
743
744 crypt_set_log_callback(cd, cryptsetup_log_glue, NULL);
745
746 r = crypt_deactivate(cd, argv[2]);
747 if (r < 0) {
748 log_error_errno(r, "Failed to deactivate: %m");
749 goto finish;
750 }
751
752 } else {
753 log_error("Unknown verb %s.", argv[1]);
754 goto finish;
755 }
756
757 r = 0;
758
759 finish:
760 free(arg_cipher);
761 free(arg_hash);
762 free(arg_header);
763 strv_free(arg_tcrypt_keyfiles);
764
765 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
766 }