main.c revision 1.4
1/*	$NetBSD: main.c,v 1.4 2019/10/13 21:56:14 joerg Exp $	*/
2
3#if HAVE_CONFIG_H
4#include "config.h"
5#endif
6#include <nbcompat.h>
7#if HAVE_SYS_CDEFS_H
8#include <sys/cdefs.h>
9#endif
10__RCSID("$NetBSD: main.c,v 1.4 2019/10/13 21:56:14 joerg Exp $");
11
12/*-
13 * Copyright (c) 1999-2019 The NetBSD Foundation, Inc.
14 * All rights reserved.
15 *
16 * This code is derived from software contributed to The NetBSD Foundation
17 * by Hubert Feyrer <hubert@feyrer.de> and
18 * by Joerg Sonnenberger <joerg@NetBSD.org>.
19 *
20 * Redistribution and use in source and binary forms, with or without
21 * modification, are permitted provided that the following conditions
22 * are met:
23 * 1. Redistributions of source code must retain the above copyright
24 *    notice, this list of conditions and the following disclaimer.
25 * 2. Redistributions in binary form must reproduce the above copyright
26 *    notice, this list of conditions and the following disclaimer in the
27 *    documentation and/or other materials provided with the distribution.
28 *
29 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
30 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
31 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
32 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
33 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
34 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
35 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
36 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
37 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
38 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
39 * POSSIBILITY OF SUCH DAMAGE.
40 */
41
42#if HAVE_SYS_TYPES_H
43#include <sys/types.h>
44#endif
45#if HAVE_SYS_STAT_H
46#include <sys/stat.h>
47#endif
48#if HAVE_DIRENT_H
49#include <dirent.h>
50#endif
51#if HAVE_ERR_H
52#include <err.h>
53#endif
54#if HAVE_ERRNO_H
55#include <errno.h>
56#endif
57#if HAVE_FCNTL_H
58#include <fcntl.h>
59#endif
60#ifndef NETBSD
61#include <nbcompat/md5.h>
62#include <nbcompat/sha2.h>
63#else
64#include <md5.h>
65#include <sha2.h>
66#endif
67#if HAVE_LIMITS_H
68#include <limits.h>
69#endif
70#if HAVE_STDIO_H
71#include <stdio.h>
72#endif
73#if HAVE_STRING_H
74#include <string.h>
75#endif
76
77#ifndef BOOTSTRAP
78#include <archive.h>
79#include <fetch.h>
80#endif
81
82#include "admin.h"
83#include "lib.h"
84
85#define DEFAULT_SFX	".t[bg]z"	/* default suffix for ls{all,best} */
86
87struct pkgdb_count {
88	size_t files;
89	size_t directories;
90	size_t packages;
91};
92
93static const char Options[] = "C:K:SVbd:qs:v";
94
95int	quiet, verbose;
96
97static void set_unset_variable(char **, Boolean);
98static void digest_input(char **);
99
100/* print usage message and exit */
101void
102usage(void)
103{
104	(void) fprintf(stderr, "usage: %s [-bqSVv] [-C config] [-d lsdir] [-K pkg_dbdir] [-s sfx] command [args ...]\n"
105	    "Where 'commands' and 'args' are:\n"
106	    " rebuild                     - rebuild pkgdb from +CONTENTS files\n"
107	    " rebuild-tree                - rebuild +REQUIRED_BY files from forward deps\n"
108	    " check [pkg ...]             - check md5 checksum of installed files\n"
109	    " add pkg ...                 - add pkg files to database\n"
110	    " set variable=value pkg ...  - set installation variable for package\n"
111	    " unset variable pkg ...      - unset installation variable for package\n"
112	    " lsall /path/to/pkgpattern   - list all pkgs matching the pattern\n"
113	    " lsbest /path/to/pkgpattern  - list pkgs matching the pattern best\n"
114	    " dump                        - dump database\n"
115	    " pmatch pattern pkg          - returns true if pkg matches pattern, otherwise false\n"
116	    " fetch-pkg-vulnerabilities [-s] - fetch new vulnerability file\n"
117	    " check-pkg-vulnerabilities [-s] <file> - check syntax and checksums of the vulnerability file\n"
118	    " audit [-eis] [-t type] ...       - check installed packages for vulnerabilities\n"
119	    " audit-pkg [-eis] [-t type] ...   - check listed packages for vulnerabilities\n"
120	    " audit-batch [-eis] [-t type] ... - check packages in listed files for vulnerabilities\n"
121	    " audit-history [-t type] ...     - print all advisories for package names\n"
122	    " check-license <condition>       - check if condition is acceptable\n"
123	    " check-single-license <license>  - check if license is acceptable\n"
124	    " config-var name                 - print current value of the configuration variable\n"
125	    " check-signature ...             - verify the signature of packages\n"
126	    " x509-sign-package pkg spkg key cert  - create X509 signature\n"
127	    " gpg-sign-package pkg spkg       - create GPG signature\n",
128	    getprogname());
129	exit(EXIT_FAILURE);
130}
131
132/*
133 * add1pkg(<pkg>)
134 *	adds the files listed in the +CONTENTS of <pkg> into the
135 *	pkgdb.byfile.db database file in the current package dbdir.  It
136 *	returns the number of files added to the database file.
137 */
138static int
139add_pkg(const char *pkgdir, void *vp)
140{
141	FILE	       *f;
142	plist_t	       *p;
143	package_t	Plist;
144	char 	       *contents;
145	char *PkgName, *dirp;
146	char 		file[MaxPathSize];
147	struct pkgdb_count *count;
148
149	if (!pkgdb_open(ReadWrite))
150		err(EXIT_FAILURE, "cannot open pkgdb");
151
152	count = vp;
153	++count->packages;
154
155	contents = pkgdb_pkg_file(pkgdir, CONTENTS_FNAME);
156	if ((f = fopen(contents, "r")) == NULL)
157		errx(EXIT_FAILURE, "%s: can't open `%s'", pkgdir, CONTENTS_FNAME);
158	free(contents);
159
160	read_plist(&Plist, f);
161	if ((p = find_plist(&Plist, PLIST_NAME)) == NULL) {
162		errx(EXIT_FAILURE, "Package `%s' has no @name, aborting.", pkgdir);
163	}
164
165	PkgName = p->name;
166	dirp = NULL;
167	for (p = Plist.head; p; p = p->next) {
168		switch(p->type) {
169		case PLIST_FILE:
170			if (dirp == NULL) {
171				errx(EXIT_FAILURE, "@cwd not yet found, please send-pr!");
172			}
173			(void) snprintf(file, sizeof(file), "%s/%s", dirp, p->name);
174			if (!(isfile(file) || islinktodir(file))) {
175				if (isbrokenlink(file)) {
176					warnx("%s: Symlink `%s' exists and is in %s but target does not exist!",
177						PkgName, file, CONTENTS_FNAME);
178				} else {
179					warnx("%s: File `%s' is in %s but not on filesystem!",
180						PkgName, file, CONTENTS_FNAME);
181				}
182			} else {
183				pkgdb_store(file, PkgName);
184				++count->files;
185			}
186			break;
187		case PLIST_PKGDIR:
188			add_pkgdir(PkgName, dirp, p->name);
189			++count->directories;
190			break;
191		case PLIST_CWD:
192			if (strcmp(p->name, ".") != 0)
193				dirp = p->name;
194			else
195				dirp = pkgdb_pkg_dir(pkgdir);
196			break;
197		case PLIST_IGNORE:
198			p = p->next;
199			break;
200		case PLIST_SHOW_ALL:
201		case PLIST_SRC:
202		case PLIST_CMD:
203		case PLIST_CHMOD:
204		case PLIST_CHOWN:
205		case PLIST_CHGRP:
206		case PLIST_COMMENT:
207		case PLIST_NAME:
208		case PLIST_UNEXEC:
209		case PLIST_DISPLAY:
210		case PLIST_PKGDEP:
211		case PLIST_DIR_RM:
212		case PLIST_OPTION:
213		case PLIST_PKGCFL:
214		case PLIST_BLDDEP:
215			break;
216		}
217	}
218	free_plist(&Plist);
219	fclose(f);
220	pkgdb_close();
221
222	return 0;
223}
224
225static void
226rebuild(void)
227{
228	char *cachename;
229	struct pkgdb_count count;
230
231	count.files = 0;
232	count.directories = 0;
233	count.packages = 0;
234
235	cachename = pkgdb_get_database();
236	if (unlink(cachename) != 0 && errno != ENOENT)
237		err(EXIT_FAILURE, "unlink %s", cachename);
238
239	setbuf(stdout, NULL);
240
241	iterate_pkg_db(add_pkg, &count);
242
243	printf("\n");
244	printf("Stored %" PRIzu " file%s and %" PRIzu " explicit director%s"
245	    " from %"PRIzu " package%s in %s.\n",
246	    count.files, count.files == 1 ? "" : "s",
247	    count.directories, count.directories == 1 ? "y" : "ies",
248	    count.packages, count.packages == 1 ? "" : "s",
249	    cachename);
250}
251
252static int
253lspattern(const char *pkg, void *vp)
254{
255	const char *dir = vp;
256	printf("%s/%s\n", dir, pkg);
257	return 0;
258}
259
260static int
261lsbasepattern(const char *pkg, void *vp)
262{
263	puts(pkg);
264	return 0;
265}
266
267static int
268remove_required_by(const char *pkgname, void *cookie)
269{
270	char *path;
271
272	path = pkgdb_pkg_file(pkgname, REQUIRED_BY_FNAME);
273
274	if (unlink(path) == -1 && errno != ENOENT)
275		err(EXIT_FAILURE, "Cannot remove %s", path);
276
277	free(path);
278
279	return 0;
280}
281
282static void
283add_required_by(const char *pattern, const char *required_by)
284{
285	char *best_installed, *path;
286	int fd;
287	size_t len;
288
289	best_installed = find_best_matching_installed_pkg(pattern);
290	if (best_installed == NULL) {
291		warnx("Dependency %s of %s unresolved", pattern, required_by);
292		return;
293	}
294
295	path = pkgdb_pkg_file(best_installed, REQUIRED_BY_FNAME);
296	free(best_installed);
297
298	if ((fd = open(path, O_WRONLY | O_APPEND | O_CREAT, 0644)) == -1)
299		errx(EXIT_FAILURE, "Cannot write to %s", path);
300	free(path);
301
302	len = strlen(required_by);
303	if (write(fd, required_by, len) != (ssize_t)len ||
304	    write(fd, "\n", 1) != 1 ||
305	    close(fd) == -1)
306		errx(EXIT_FAILURE, "Cannot write to %s", path);
307}
308
309
310static int
311add_depends_of(const char *pkgname, void *cookie)
312{
313	FILE *fp;
314	plist_t *p;
315	package_t plist;
316	char *path;
317
318	path = pkgdb_pkg_file(pkgname, CONTENTS_FNAME);
319	if ((fp = fopen(path, "r")) == NULL)
320		errx(EXIT_FAILURE, "Cannot read %s of package %s",
321		    CONTENTS_FNAME, pkgname);
322	free(path);
323	read_plist(&plist, fp);
324	fclose(fp);
325
326	for (p = plist.head; p; p = p->next) {
327		if (p->type == PLIST_PKGDEP)
328			add_required_by(p->name, pkgname);
329	}
330
331	free_plist(&plist);
332
333	return 0;
334}
335
336static void
337rebuild_tree(void)
338{
339	if (iterate_pkg_db(remove_required_by, NULL) == -1)
340		errx(EXIT_FAILURE, "cannot iterate pkgdb");
341	if (iterate_pkg_db(add_depends_of, NULL) == -1)
342		errx(EXIT_FAILURE, "cannot iterate pkgdb");
343}
344
345int
346main(int argc, char *argv[])
347{
348	Boolean		 use_default_sfx = TRUE;
349	Boolean 	 show_basename_only = FALSE;
350	char		 lsdir[MaxPathSize];
351	char		 sfx[MaxPathSize];
352	char		*lsdirp = NULL;
353	int		 ch;
354
355	setprogname(argv[0]);
356
357	if (argc < 2)
358		usage();
359
360	while ((ch = getopt(argc, argv, Options)) != -1)
361		switch (ch) {
362		case 'C':
363			config_file = optarg;
364			break;
365
366		case 'K':
367			pkgdb_set_dir(optarg, 3);
368			break;
369
370		case 'S':
371			sfx[0] = 0x0;
372			use_default_sfx = FALSE;
373			break;
374
375		case 'V':
376			show_version();
377			/* NOTREACHED */
378
379		case 'b':
380			show_basename_only = TRUE;
381			break;
382
383		case 'd':
384			(void) strlcpy(lsdir, optarg, sizeof(lsdir));
385			lsdirp = lsdir;
386			break;
387
388		case 'q':
389			quiet = 1;
390			break;
391
392		case 's':
393			(void) strlcpy(sfx, optarg, sizeof(sfx));
394			use_default_sfx = FALSE;
395			break;
396
397		case 'v':
398			++verbose;
399			break;
400
401		default:
402			usage();
403			/* NOTREACHED */
404		}
405
406	argc -= optind;
407	argv += optind;
408
409	if (argc <= 0) {
410		usage();
411	}
412
413	/*
414	 * config-var is reading the config file implicitly,
415	 * so skip it here.
416	 */
417	if (strcasecmp(argv[0], "config-var") != 0)
418		pkg_install_config();
419
420	if (use_default_sfx)
421		(void) strlcpy(sfx, DEFAULT_SFX, sizeof(sfx));
422
423	if (strcasecmp(argv[0], "pmatch") == 0) {
424
425		char *pattern, *pkg;
426
427		argv++;		/* "pmatch" */
428
429		if (argv[0] == NULL || argv[1] == NULL) {
430			usage();
431		}
432
433		pattern = argv[0];
434		pkg = argv[1];
435
436		if (pkg_match(pattern, pkg)){
437			return 0;
438		} else {
439			return 1;
440		}
441
442	} else if (strcasecmp(argv[0], "rebuild") == 0) {
443
444		rebuild();
445		printf("Done.\n");
446
447
448	} else if (strcasecmp(argv[0], "rebuild-tree") == 0) {
449
450		rebuild_tree();
451		printf("Done.\n");
452
453	} else if (strcasecmp(argv[0], "check") == 0) {
454		argv++;		/* "check" */
455
456		check(argv);
457
458		if (!quiet) {
459			printf("Done.\n");
460		}
461
462	} else if (strcasecmp(argv[0], "lsall") == 0) {
463		argv++;		/* "lsall" */
464
465		while (*argv != NULL) {
466			/* args specified */
467			int     rc;
468			const char *basep, *dir;
469
470			dir = lsdirp ? lsdirp : dirname_of(*argv);
471			basep = basename_of(*argv);
472
473			if (show_basename_only)
474				rc = match_local_files(dir, use_default_sfx, 1, basep, lsbasepattern, NULL);
475			else
476				rc = match_local_files(dir, use_default_sfx, 1, basep, lspattern, __UNCONST(dir));
477			if (rc == -1)
478				errx(EXIT_FAILURE, "Error from match_local_files(\"%s\", \"%s\", ...)",
479				     dir, basep);
480
481			argv++;
482		}
483
484	} else if (strcasecmp(argv[0], "lsbest") == 0) {
485		argv++;		/* "lsbest" */
486
487		while (*argv != NULL) {
488			/* args specified */
489			const char *basep, *dir;
490			char *p;
491
492			dir = lsdirp ? lsdirp : dirname_of(*argv);
493			basep = basename_of(*argv);
494
495			p = find_best_matching_file(dir, basep, use_default_sfx, 1);
496
497			if (p) {
498				if (show_basename_only)
499					printf("%s\n", p);
500				else
501					printf("%s/%s\n", dir, p);
502				free(p);
503			}
504
505			argv++;
506		}
507	} else if (strcasecmp(argv[0], "list") == 0 ||
508	    strcasecmp(argv[0], "dump") == 0) {
509
510		pkgdb_dump();
511
512	} else if (strcasecmp(argv[0], "add") == 0) {
513		struct pkgdb_count count;
514
515		count.files = 0;
516		count.directories = 0;
517		count.packages = 0;
518
519		for (++argv; *argv != NULL; ++argv)
520			add_pkg(*argv, &count);
521	} else if (strcasecmp(argv[0], "set") == 0) {
522		argv++;		/* "set" */
523		set_unset_variable(argv, FALSE);
524	} else if (strcasecmp(argv[0], "unset") == 0) {
525		argv++;		/* "unset" */
526		set_unset_variable(argv, TRUE);
527	} else if (strcasecmp(argv[0], "digest") == 0) {
528		argv++;		/* "digest" */
529		digest_input(argv);
530	} else if (strcasecmp(argv[0], "config-var") == 0) {
531		argv++;
532		if (argv == NULL || argv[1] != NULL)
533			errx(EXIT_FAILURE, "config-var takes exactly one argument");
534		pkg_install_show_variable(argv[0]);
535	} else if (strcasecmp(argv[0], "check-license") == 0) {
536		if (argv[1] == NULL)
537			errx(EXIT_FAILURE, "check-license takes exactly one argument");
538
539		load_license_lists();
540
541		switch (acceptable_pkg_license(argv[1])) {
542		case 0:
543			puts("no");
544			return 0;
545		case 1:
546			puts("yes");
547			return 0;
548		case -1:
549			errx(EXIT_FAILURE, "invalid license condition");
550		}
551	} else if (strcasecmp(argv[0], "check-single-license") == 0) {
552		if (argv[1] == NULL)
553			errx(EXIT_FAILURE, "check-license takes exactly one argument");
554		load_license_lists();
555
556		switch (acceptable_license(argv[1])) {
557		case 0:
558			puts("no");
559			return 0;
560		case 1:
561			puts("yes");
562			return 0;
563		case -1:
564			errx(EXIT_FAILURE, "invalid license");
565		}
566	}
567#ifndef BOOTSTRAP
568	else if (strcasecmp(argv[0], "findbest") == 0) {
569		struct url *url;
570		char *output;
571		int rc;
572
573		process_pkg_path();
574
575		rc = 0;
576		for (++argv; *argv != NULL; ++argv) {
577			url = find_best_package(NULL, *argv, 1);
578			if (url == NULL) {
579				rc = 1;
580				continue;
581			}
582			output = fetchStringifyURL(url);
583			puts(output);
584			fetchFreeURL(url);
585			free(output);
586		}
587
588		return rc;
589	} else if (strcasecmp(argv[0], "fetch-pkg-vulnerabilities") == 0) {
590		fetch_pkg_vulnerabilities(--argc, ++argv);
591	} else if (strcasecmp(argv[0], "check-pkg-vulnerabilities") == 0) {
592		check_pkg_vulnerabilities(--argc, ++argv);
593	} else if (strcasecmp(argv[0], "audit") == 0) {
594		audit_pkgdb(--argc, ++argv);
595	} else if (strcasecmp(argv[0], "audit-pkg") == 0) {
596		audit_pkg(--argc, ++argv);
597	} else if (strcasecmp(argv[0], "audit-batch") == 0) {
598		audit_batch(--argc, ++argv);
599	} else if (strcasecmp(argv[0], "audit-history") == 0) {
600		audit_history(--argc, ++argv);
601	} else if (strcasecmp(argv[0], "check-signature") == 0) {
602		struct archive *pkg;
603		int rc;
604
605		rc = 0;
606		for (--argc, ++argv; argc > 0; --argc, ++argv) {
607			char *archive_name;
608
609			pkg = open_archive(*argv, &archive_name);
610			if (pkg == NULL) {
611				warnx("%s could not be opened", *argv);
612				continue;
613			}
614			if (pkg_full_signature_check(archive_name, &pkg))
615				rc = 1;
616			free(archive_name);
617			if (pkg != NULL)
618				archive_read_free(pkg);
619		}
620		return rc;
621	} else if (strcasecmp(argv[0], "x509-sign-package") == 0) {
622#ifdef HAVE_SSL
623		--argc;
624		++argv;
625		if (argc != 4)
626			errx(EXIT_FAILURE, "x509-sign-package takes exactly four arguments");
627		pkg_sign_x509(argv[0], argv[1], argv[2], argv[3]);
628#else
629		errx(EXIT_FAILURE, "OpenSSL support is not included");
630#endif
631	} else if (strcasecmp(argv[0], "gpg-sign-package") == 0) {
632		--argc;
633		++argv;
634		if (argc != 2)
635			errx(EXIT_FAILURE, "gpg-sign-package takes exactly two arguments");
636		pkg_sign_gpg(argv[0], argv[1]);
637	}
638#endif
639	else {
640		usage();
641	}
642
643	return 0;
644}
645
646struct set_installed_info_arg {
647	char *variable;
648	char *value;
649	int got_match;
650};
651
652static int
653set_installed_info_var(const char *name, void *cookie)
654{
655	struct set_installed_info_arg *arg = cookie;
656	char *filename;
657	int retval;
658
659	filename = pkgdb_pkg_file(name, INSTALLED_INFO_FNAME);
660
661	retval = var_set(filename, arg->variable, arg->value);
662
663	free(filename);
664	arg->got_match = 1;
665
666	return retval;
667}
668
669static void
670set_unset_variable(char **argv, Boolean unset)
671{
672	struct set_installed_info_arg arg;
673	char *eq;
674	char *variable;
675	int ret = 0;
676
677	if (argv[0] == NULL || argv[1] == NULL)
678		usage();
679
680	variable = NULL;
681
682	if (unset) {
683		arg.variable = argv[0];
684		arg.value = NULL;
685	} else {
686		eq = NULL;
687		if ((eq=strchr(argv[0], '=')) == NULL)
688			usage();
689
690		variable = xmalloc(eq-argv[0]+1);
691		strlcpy(variable, argv[0], eq-argv[0]+1);
692
693		arg.variable = variable;
694		arg.value = eq+1;
695
696		if (strcmp(variable, AUTOMATIC_VARNAME) == 0 &&
697		    strcasecmp(arg.value, "yes") != 0 &&
698		    strcasecmp(arg.value, "no") != 0) {
699			errx(EXIT_FAILURE,
700			     "unknown value `%s' for " AUTOMATIC_VARNAME,
701			     arg.value);
702		}
703	}
704	if (strpbrk(arg.variable, "ABCDEFGHIJKLMNOPQRSTUVWXYZ") != NULL) {
705		free(variable);
706		errx(EXIT_FAILURE,
707		     "variable name must not contain uppercase letters");
708	}
709
710	argv++;
711	while (*argv != NULL) {
712		arg.got_match = 0;
713		if (match_installed_pkgs(*argv, set_installed_info_var, &arg) == -1)
714			errx(EXIT_FAILURE, "Cannot process pkdbdb");
715		if (arg.got_match == 0) {
716			char *pattern;
717
718			if (ispkgpattern(*argv)) {
719				warnx("no matching pkg for `%s'", *argv);
720				ret++;
721			} else {
722				pattern = xasprintf("%s-[0-9]*", *argv);
723
724				if (match_installed_pkgs(pattern, set_installed_info_var, &arg) == -1)
725					errx(EXIT_FAILURE, "Cannot process pkdbdb");
726
727				if (arg.got_match == 0) {
728					warnx("cannot find package %s", *argv);
729					++ret;
730				}
731				free(pattern);
732			}
733		}
734
735		argv++;
736	}
737
738	if (ret > 0)
739		exit(EXIT_FAILURE);
740
741	free(variable);
742
743	return;
744}
745
746static void
747digest_input(char **argv)
748{
749	char digest[SHA256_DIGEST_STRING_LENGTH];
750	int failures = 0;
751
752	while (*argv != NULL) {
753		if (SHA256_File(*argv, digest)) {
754			puts(digest);
755		} else {
756			warn("cannot process %s", *argv);
757			++failures;
758		}
759		argv++;
760	}
761	if (failures)
762		exit(EXIT_FAILURE);
763}
764