xref: /freebsd/usr.sbin/sysconf/sysconf.c (revision 0380d016d9ba407af12eb743b7c5806d49ddf848)
1 /*
2  * Copyright (c) 2013-2026 Devin Teske <dteske@FreeBSD.org>
3  * Copyright (c) 2021-2026 Faraz Vahedi <kfv@FreeBSD.org>
4  *
5  * SPDX-License-Identifier: BSD-2-Clause
6  */
7 
8 /*
9  * sysconf(8): CLI entry, option parsing, and program state.
10  */
11 
12 #include "sysconf_priv.h"
13 
14 /* Requests to process (one per name argument) */
15 struct request	*reqs;		/* set by main() */
16 unsigned int	nreqs;		/* set by main() */
17 
18 /* Union of all directives, for `-a' against multi-file targets */
19 struct dumpent	*dumps;		/* set by scan_cb() */
20 size_t		ndumps;		/* set by scan_cb() */
21 size_t		dumpsize;	/* set by scan_cb() */
22 
23 /* Resolved target state */
24 char		**conf_files;	/* ordered backing files */
25 size_t		nconf_files;
26 size_t		conf_scanidx;	/* file being parsed (scan_pass) */
27 size_t		write_idx;	/* default write target index */
28 int		defaults_idx = -1; /* defaults file index in conf_files
29 		                    * (read-only, never listed); -1 if none */
30 enum bsdconf_format format = BSDCONF_FORMAT_GENERIC;
31 
32 /* Extra display information */
33 const char	*pgm;			/* set to getprogname() by main() */
34 bool		check = false;		/* `-c' */
35 bool		defaults_only = false;	/* `-D' */
36 bool		dump_all = false;	/* `-a' */
37 bool		existing_only = false;	/* `-E' */
38 bool		ignore_unknown = false;	/* `-i' */
39 bool		list_all = false;	/* `-L' */
40 bool		list_files = false;	/* `-l' */
41 bool		name_only = false;	/* `-N' */
42 bool		quiet = false;		/* `-q' */
43 bool		remove_mode = false;	/* `-x' */
44 bool		set_knobs = false;	/* `-s' WITH_/WITHOUT_ presence */
45 bool		show_desc = false;	/* `-d' */
46 bool		show_equals = false;	/* `-e' */
47 bool		show_file = false;	/* `-F' */
48 bool		value_only = false;	/* `-n' */
49 bool		verbose = false;	/* `-v' */
50 bool		verbose_trail = false;	/* `-V' */
51 bool		with_defaults = false;	/* `-A' */
52 
53 /* Option arguments */
54 const char	*file = NULL;		/* `-f file' */
55 bool		file_stdin = false;	/* `-f -' given */
56 const char	*jailname = NULL;	/* `-j jail' */
57 const char	*module = NULL;		/* `-k module' */
58 const char	*rootdir = "";		/* `-R dir' */
59 
60 /*
61  * Locate the target keyword in `argv' without consuming or validating
62  * anything: the first argument that is neither an option cluster nor the
63  * argument of one (per OPTSTRING; an unknown option letter is assumed to
64  * take no argument). A `--' ends option scanning as usual. Returns the
65  * argv index of the target, or -1 when there is none. Used before any
66  * getopt(3) processing so that the `rc' pass-through target (see
67  * exec_sysrc() below) can be detected while the command line is still
68  * pristine.
69  *
70  * Attached arguments (`-fPATH', `-R/altroot') are self-contained: the rest
71  * of the cluster is the option's argument, so the following argv element is
72  * not consumed. Only a trailing option letter that takes an argument and
73  * has nothing after it in the cluster (`-f PATH') skips the next element.
74  */
75 static int
find_target(int argc,char * argv[])76 find_target(int argc, char *argv[])
77 {
78 	int n;
79 	const char *o;
80 	const char *p;
81 
82 	for (n = 1; n < argc; n++) {
83 		if (strcmp(argv[n], "--") == 0)
84 			return (n + 1 < argc ? n + 1 : -1);
85 		if (argv[n][0] == '-' && argv[n][1] != '\0') {
86 			for (p = argv[n] + 1; *p != '\0'; p++) {
87 				o = strchr(OPTSTRING, *p);
88 				if (o != NULL && o[1] == ':') {
89 					/* Takes an argument */
90 					if (p[1] != '\0') {
91 						;
92 						/* attached: rest of argv[n] */
93 					} else if (n + 1 < argc) {
94 						n++;
95 						/* separate argv */
96 					}
97 					break;
98 				}
99 			}
100 			continue;
101 		}
102 		return (n);
103 	}
104 
105 	return (-1);
106 }
107 
108 /*
109  * The `rc' pass-through: rc.conf(5) is sysrc(8)'s domain, and its feature
110  * set is a superset of ours, so no option or argument is validated,
111  * interpreted, or reordered here -- every argument except the target
112  * keyword itself (at argv index `skip') is handed to sysrc(8) verbatim.
113  * Never returns.
114  */
115 static void
exec_sysrc(int argc,char * argv[],int skip)116 exec_sysrc(int argc, char *argv[], int skip)
117 {
118 	int n;
119 	int nargc = 0;
120 	char **nargv;
121 
122 	if ((nargv = calloc((size_t)argc + 1, sizeof(*nargv))) == NULL)
123 		err(EXIT_FAILURE, NULL);
124 	nargv[nargc++] = (char *)(uintptr_t)"sysrc";
125 	for (n = 1; n < argc; n++) {
126 		if (n == skip)
127 			continue;
128 		nargv[nargc++] = argv[n];
129 	}
130 	nargv[nargc] = NULL;
131 
132 	execvp("sysrc", nargv);
133 	err(EXIT_FAILURE, "sysrc");
134 }
135 
136 /*
137  * Run the getopt(3) loop over `argv', setting the option globals. Called
138  * once for the options preceding the target keyword and once for those
139  * following it. Returns the number of arguments consumed (optind).
140  */
141 static int
parse_options(int argc,char * argv[])142 parse_options(int argc, char *argv[])
143 {
144 	int ch;
145 
146 	while ((ch = getopt(argc, argv, OPTSTRING)) != -1) {
147 		switch (ch) {
148 		case 'A': /* dump all directives, defaults included */
149 			with_defaults = 1;
150 			break;
151 		case 'a': /* dump all directives */
152 			dump_all = 1;
153 			break;
154 		case 'c': /* check only; do not modify */
155 			check = 1;
156 			break;
157 		case 'd': /* show directive descriptions */
158 			show_desc = 1;
159 			break;
160 		case 'D': /* consult the defaults file only */
161 			defaults_only = 1;
162 			break;
163 		case 'E': /* list existing files only (with -l/-L) */
164 			existing_only = 1;
165 			break;
166 		case 'e': /* show name=value */
167 			show_equals = 1;
168 			break;
169 		case 'F': /* show authoritative file, not value */
170 			show_file = 1;
171 			break;
172 		case 'f': /* explicit file (`-' is standard input) */
173 			if (strcmp(optarg, "-") == 0) {
174 				file = "/dev/stdin";
175 				file_stdin = 1;
176 			} else {
177 				file = optarg;
178 				file_stdin = 0;
179 			}
180 			break;
181 		case 'h': /* help/usage */
182 			usage();
183 			break;
184 		case 'i': /* ignore unknown names */
185 			ignore_unknown = 1;
186 			break;
187 		case 'j': /* jail name or id */
188 			jailname = optarg;
189 			break;
190 		case 'k': /* kernel module drop-in */
191 			module = optarg;
192 			break;
193 		case 'l': /* list backing files */
194 			list_files = 1;
195 			break;
196 		case 'L': /* list all candidate files */
197 			list_all = 1;
198 			break;
199 		case 'n': /* value only */
200 			value_only = 1;
201 			break;
202 		case 'N': /* name only */
203 			name_only = 1;
204 			break;
205 		case 'q': /* quiet */
206 			quiet = 1;
207 			break;
208 		case 'R': /* root dir */
209 			rootdir = optarg;
210 			break;
211 		case 's': /* set empty WITH_/WITHOUT_ knobs (make/src) */
212 			set_knobs = 1;
213 			break;
214 		case 'v': /* verbose (last file + final value) */
215 			verbose = 1;
216 			break;
217 		case 'V': /* verbose trail (every assignment step) */
218 			verbose_trail = 1;
219 			break;
220 		case 'x': /* remove */
221 			remove_mode = 1;
222 			break;
223 		case '?': /* unknown argument (based on optstring) */
224 		default: /* unhandled argument (based on switch) */
225 			usage();
226 		}
227 	}
228 
229 	return (optind);
230 }
231 
232 /*
233  * The native configuration trinity's third member: read and modify
234  * loader.conf(5), sysctl.conf(5), make.conf(5), and arbitrary configuration
235  * files with sysctl(8) assignment syntax.
236  */
237 int
main(int argc,char * argv[])238 main(int argc, char *argv[])
239 {
240 	int have_reads = 0;
241 	int have_writes = 0;
242 	int n;
243 	int rv = EXIT_SUCCESS;
244 	const char *target = NULL;
245 
246 	pgm = getprogname();
247 
248 	/*
249 	 * The `rc' target is a pure pass-through to sysrc(8), detected
250 	 * while the command line is still pristine so that nothing --
251 	 * `--help' and `--version' included -- is intercepted on its way
252 	 * there (see exec_sysrc()).
253 	 */
254 	n = find_target(argc, argv);
255 	if (n > 0 && strcmp(argv[n], "rc") == 0)
256 		exec_sysrc(argc, argv, n); /* never returns */
257 
258 	/*
259 	 * Honor `--help' and `--version' wherever they appear (getopt(3)
260 	 * knows nothing of long options and would reject them as illegal
261 	 * short options). A bare `--' ends option processing as usual.
262 	 */
263 	for (n = 1; n < argc; n++) {
264 		if (strcmp(argv[n], "--") == 0)
265 			break;
266 		if (strcmp(argv[n], "--help") == 0)
267 			help();
268 		if (strcmp(argv[n], "--version") == 0) {
269 			printf("%s (libbsdconf %s)\n", SYSCONF_VERSION,
270 			    BSDCONF_VERSION);
271 			exit(EXIT_SUCCESS);
272 		}
273 	}
274 
275 	/*
276 	 * Process command-line options
277 	 */
278 	n = parse_options(argc, argv);
279 	argc -= n;
280 	argv += n;
281 
282 	/*
283 	 * Consume the (required) target keyword and allow further options
284 	 * to follow it, mirroring the option placement freedom of
285 	 * sysctl(8) (e.g., `sysconf sysctl -L'). getopt(3) skips over its
286 	 * first argument, so hand it the target keyword's slot as the
287 	 * program name.
288 	 */
289 	if (argc > 0) {
290 		target = argv[0];
291 		argc--;
292 		argv++;
293 #ifdef __FreeBSD__
294 		optreset = 1;
295 		optind = 1;
296 #else
297 		optind = 0; /* glibc and musl re-initialize at zero */
298 #endif
299 		n = parse_options(argc + 1, argv - 1) - 1;
300 		argc -= n;
301 		argv += n;
302 	}
303 
304 	if (jailname != NULL && *rootdir != '\0') {
305 		warnx("-j and -R are mutually exclusive");
306 		usage();
307 	}
308 	if (file != NULL && module != NULL) {
309 		warnx("-f and -k are mutually exclusive");
310 		usage();
311 	}
312 	if (existing_only && !list_files && !list_all) {
313 		warnx("-E requires -l or -L");
314 		usage();
315 	}
316 	if (set_knobs && remove_mode) {
317 		warnx("-s and -x are mutually exclusive");
318 		usage();
319 	}
320 
321 #ifdef __FreeBSD__
322 	if (jailname != NULL) {
323 		int jid;
324 
325 		if ((jid = jail_getid(jailname)) < 0)
326 			errx(EXIT_FAILURE, "%s", jail_errmsg);
327 		if (jail_attach(jid) != 0)
328 			err(EXIT_FAILURE, "jail_attach(%d)", jid);
329 	}
330 #else
331 	if (jailname != NULL)
332 		errx(EXIT_FAILURE, "-j is not supported on this platform");
333 #endif
334 
335 	/* Resolve the target format and its backing files */
336 	resolve_target(target);
337 
338 	/* `-k' requires a target with a module drop-in directory */
339 	if (module != NULL) {
340 		const struct bsdconf_format_def *def;
341 		const struct bsdconf_source *source;
342 		int has_moddir = 0;
343 
344 		def = bsdconf_format_lookup(format);
345 		if (def != NULL && def->sources != NULL)
346 			for (source = def->sources; source->path != NULL;
347 			    source++)
348 				if (source->type == BSDCONF_SOURCE_MODDIR)
349 					has_moddir = 1;
350 		if (!has_moddir) {
351 			warnx("target does not support -k");
352 			usage();
353 		}
354 	}
355 
356 	/*
357 	 * `-d', `-D', and `-A' consult a body of defaults; only targets
358 	 * that have one support them (make.conf and friends have no
359 	 * defaults file, and no directive descriptions to give). The
360 	 * exception is `sysctl -d', whose descriptions come from the
361 	 * running kernel rather than any file.
362 	 */
363 	{
364 		const struct bsdconf_format_def *def;
365 		int has_defaults;
366 
367 		def = bsdconf_format_lookup(format);
368 		has_defaults = def != NULL && def->defaults != NULL;
369 
370 		if (show_desc) {
371 			if (format != BSDCONF_FORMAT_SYSCTL &&
372 			    !has_defaults) {
373 				warnx("target does not support -d");
374 				usage();
375 			}
376 			if (file != NULL) {
377 				warnx("-d and -f are mutually exclusive");
378 				usage();
379 			}
380 			if (list_files || list_all || remove_mode || check ||
381 			    set_knobs) {
382 				warnx("-d cannot be combined with "
383 				    "-c/-l/-L/-s/-x");
384 				usage();
385 			}
386 #ifdef __FreeBSD__
387 			if (format == BSDCONF_FORMAT_SYSCTL &&
388 			    *rootdir != '\0')
389 				errx(EXIT_FAILURE, "sysctl descriptions "
390 				    "come from the running kernel; "
391 				    "-R does not apply");
392 #endif
393 		}
394 		if ((defaults_only || with_defaults) && !has_defaults) {
395 			warnx("target does not support -%c",
396 			    defaults_only ? 'D' : 'A');
397 			usage();
398 		}
399 		if ((defaults_only || with_defaults) && file != NULL) {
400 			warnx("-%c and -f are mutually exclusive",
401 			    defaults_only ? 'D' : 'A');
402 			usage();
403 		}
404 	}
405 
406 	/* File listings take no names and perform no other work */
407 	if (list_files || list_all) {
408 		if (argc != 0 || dump_all) {
409 			warnx("-l/-L take no names");
410 			usage();
411 		}
412 		exit(do_list());
413 	}
414 
415 	/* `-A' with no names dumps everything, defaults included */
416 	if (with_defaults && argc == 0)
417 		dump_all = 1;
418 
419 	/* Display usage and exit if not given at least one name */
420 	if (argc == 0 && !dump_all) {
421 		warnx("no names provided");
422 		usage();
423 	}
424 	if (argc != 0 && dump_all) {
425 		warnx("-a does not take names");
426 		usage();
427 	}
428 
429 	/* Classify the remaining arguments */
430 	nreqs = (unsigned int)argc;
431 	if (nreqs > 0 &&
432 	    (reqs = calloc(nreqs, sizeof(*reqs))) == NULL)
433 		err(EXIT_FAILURE, NULL);
434 	if (set_knobs) {
435 		if (format != BSDCONF_FORMAT_MAKE &&
436 		    format != BSDCONF_FORMAT_SRC) {
437 			warnx("-s is only valid for make and src");
438 			usage();
439 		}
440 		if (argc == 0) {
441 			warnx("-s requires at least one WITH_/WITHOUT_ name");
442 			usage();
443 		}
444 	}
445 	for (n = 0; n < argc; n++) {
446 		split_request(argv[n], &reqs[n], remove_mode);
447 		if (set_knobs) {
448 			if (reqs[n].newvalue != NULL) {
449 				warnx("-s does not take a value "
450 				    "('%s')", argv[n]);
451 				usage();
452 			}
453 			if (!make_knob_name_p(reqs[n].name)) {
454 				if (strcmp(reqs[n].name,
455 				    "WITHOUT_MODULES") == 0)
456 					warnx("WITHOUT_MODULES takes a "
457 					    "module list; use "
458 					    "WITHOUT_MODULES=...");
459 				else
460 					warnx("-s requires a WITH_ or "
461 					    "WITHOUT_ name ('%s')",
462 					    reqs[n].name);
463 				usage();
464 			}
465 			reqs[n].newvalue = "";
466 		}
467 			if (reqs[n].newvalue != NULL && !reqs[n].remove)
468 				warn_with_equals_no(reqs[n].name,
469 				    reqs[n].newvalue, NULL, 0);
470 		if (reqs[n].remove || reqs[n].newvalue != NULL)
471 			have_writes = 1;
472 		else
473 			have_reads = 1;
474 	}
475 	if (check && !have_writes) {
476 		warnx("-c requires at least one name=value or -s name");
477 		usage();
478 	}
479 
480 	/* Descriptions are read-only */
481 	if (show_desc) {
482 		if (have_writes) {
483 			warnx("-d does not take values");
484 			usage();
485 		}
486 #ifndef __FreeBSD__
487 		if (format == BSDCONF_FORMAT_SYSCTL)
488 			errx(EXIT_FAILURE,
489 			    "sysctl descriptions require FreeBSD");
490 #endif
491 		/* Explicit names need no file scan at all */
492 		if (!dump_all) {
493 #ifdef __FreeBSD__
494 			if (format == BSDCONF_FORMAT_SYSCTL)
495 				exit(describe_sysctl());
496 #endif
497 			exit(describe_defaults());
498 		}
499 
500 		/*
501 		 * Dumps (-ad, -Ad, -aDd) pair each scanned directive with
502 		 * its description; harvest the descriptions before the
503 		 * read sandbox slams shut.
504 		 */
505 		if (format != BSDCONF_FORMAT_SYSCTL)
506 			load_descriptions();
507 	}
508 
509 	/* Defaults can be read and checked against, never rewritten */
510 	if ((defaults_only || with_defaults) && have_writes && !check)
511 		errx(EXIT_FAILURE, "defaults are read-only");
512 
513 	/* Standard input can be read and checked, never rewritten */
514 	if (file_stdin && have_writes && !check)
515 		errx(EXIT_FAILURE, "cannot write to standard input");
516 
517 #if defined(__FreeBSD__)
518 	/*
519 	 * Refuse up front to set sysctl OIDs that can never take effect
520 	 * from sysctl.conf(5) (non-leaf, read-only, loader-only tunables)
521 	 * or whose values cannot fit the OID's CTLTYPE (so an overflowing
522 	 * assignment cannot surprise sysctl(8) at boot). Unknown OIDs are
523 	 * warned about but still written -- the module may not be loaded
524 	 * yet, matching init(8)'s handling of sysctl.conf(5). Only
525 	 * meaningful against the running system's sysctl tree, so skipped
526 	 * under -R. -i/-q quiet the unknown-OID warning; -k remains the
527 	 * module drop-in selector.
528 	 */
529 	if (format == BSDCONF_FORMAT_SYSCTL && have_writes && !check &&
530 	    *rootdir == '\0') {
531 		int quiet_unknown = ignore_unknown || quiet;
532 		unsigned int u;
533 
534 		for (u = 0; u < nreqs; u++) {
535 			if (reqs[u].newvalue == NULL)
536 				continue;
537 			if (sysctl_writable(reqs[u].name, reqs[u].newvalue,
538 			    quiet_unknown) != 0) {
539 				/* Convert to a no-op; count the failure */
540 				reqs[u].newvalue = NULL;
541 				reqs[u].remove = 0;
542 				reqs[u].name = "";
543 				rv = EXIT_FAILURE;
544 			}
545 		}
546 		have_writes = 0;
547 		have_reads = 0;
548 		for (u = 0; u < nreqs; u++) {
549 			if (reqs[u].remove || reqs[u].newvalue != NULL)
550 				have_writes = 1;
551 			else if (reqs[u].name[0] != '\0')
552 				have_reads = 1;
553 		}
554 		if (rv != EXIT_SUCCESS && !have_writes && !have_reads)
555 			exit(rv); /* nothing settable remains */
556 	}
557 #endif
558 
559 	/*
560 	 * Scan the backing files to locate each request's authoritative
561 	 * definition. Pure reads and -c checks (do_checks() never calls
562 	 * put) run the scan inside a Capsicum sandbox -- except a sysctl
563 	 * description dump, whose kernel queries the sandbox would deny.
564 	 * Classify by whether we will actually write, not by whether the
565 	 * argv looked like name=value (`-c' sets have_writes for checks).
566 	 */
567 	scan_pass((check || !have_writes) &&
568 	    !(show_desc && format == BSDCONF_FORMAT_SYSCTL));
569 
570 	/* Materialize list edits now that effective values are known */
571 	if (merge_list_requests() != EXIT_SUCCESS)
572 		rv = EXIT_FAILURE;
573 
574 	if (check) {
575 		if (do_make_strikes(1) != EXIT_SUCCESS)
576 			rv = EXIT_FAILURE;
577 		if (do_checks() != EXIT_SUCCESS)
578 			rv = EXIT_FAILURE;
579 		exit(rv);
580 	}
581 
582 	/* Apply writes first so that subsequent reads see the result */
583 	if (have_writes) {
584 		int wrv;
585 
586 		if ((wrv = do_make_strikes(0)) != EXIT_SUCCESS)
587 			rv = wrv;
588 		if ((wrv = do_writes()) != EXIT_SUCCESS)
589 			rv = wrv;
590 
591 		/* Refresh the scan for any trailing read requests */
592 		if (have_reads || dump_all) {
593 			for (n = 0; (unsigned int)n < nreqs; n++) {
594 				size_t ai;
595 
596 				trail_free(reqs[n].trail, reqs[n].ntrail);
597 				reqs[n].trail = NULL;
598 				reqs[n].ntrail = 0;
599 				for (ai = 0; ai < reqs[n].nassigns; ai++)
600 					free(reqs[n].assigns[ai].piece);
601 				free(reqs[n].assigns);
602 				reqs[n].assigns = NULL;
603 				reqs[n].nassigns = 0;
604 				reqs[n].found = 0;
605 				reqs[n].append_present = 0;
606 				reqs[n].srcidx = -1;
607 				reqs[n].line = 0;
608 				memset(reqs[n].infile, 0, nconf_files);
609 			}
610 			scan_pass(0);
611 		}
612 	}
613 
614 	if (have_reads || dump_all) {
615 		int rrv;
616 
617 		if ((rrv = do_reads()) != EXIT_SUCCESS)
618 			rv = rrv;
619 	}
620 
621 	exit(rv);
622 }
623 
624 /*
625  * Print short usage statement to stderr and exit with error status.
626  */
627 void
usage(void)628 usage(void)
629 {
630 
631 	fprintf(stderr,
632 	    "usage: %s target [-AcDeFinNqsvVx] [-j jail | -R dir]"
633 	    " [-f file | -k module]\n"
634 	    "               name[[+|-]=value] ...\n", pgm);
635 	fprintf(stderr,
636 	    "       %s target [-ADeFnNqvV] [-j jail | -R dir]"
637 	    " [-f file | -k module] -a\n", pgm);
638 	fprintf(stderr,
639 	    "       %s target [-AaDinNq] [-j jail | -R dir]"
640 	    " -d [name ...]\n", pgm);
641 	fprintf(stderr,
642 	    "       %s target [-E] [-j jail | -R dir] [-k module]"
643 	    " -l | -L\n", pgm);
644 	fprintf(stderr,
645 	    "       %s rc [sysrc(8) argument ...]\n", pgm);
646 	fprintf(stderr, "Try `%s --help' for more information.\n", pgm);
647 	exit(EXIT_FAILURE);
648 }
649 
650 /*
651  * Print long usage statement to stderr and exit with error status.
652  */
653 void
help(void)654 help(void)
655 {
656 
657 	fprintf(stderr,
658 	    "usage: %s target [-AcDeFinNqsvVx] [-j jail | -R dir]"
659 	    " [-f file | -k module] name[[+|-]=value] ...\n", pgm);
660 	fprintf(stderr, "TARGETS:\n");
661 #define TGTFMT "\t%-9s %s\n"
662 	fprintf(stderr, TGTFMT, "loader",
663 	    "files named by loader_conf_files et al. in");
664 	fprintf(stderr, TGTFMT, "",
665 	    "/boot/defaults/loader.conf (typically /boot/loader.conf,");
666 	fprintf(stderr, TGTFMT, "",
667 	    "loader.conf.d/*.conf, loader.conf.local)");
668 	fprintf(stderr, TGTFMT, "sysctl",
669 	    "/etc/sysctl.conf, sysctl.conf.local, sysctl.kld.d/*.conf");
670 	fprintf(stderr, TGTFMT, "make",
671 	    "/etc/make.conf (or -f for any make-syntax file)");
672 	fprintf(stderr, TGTFMT, "src",
673 	    "/usr/src build triad: src-env.conf, make.conf, src.conf");
674 	fprintf(stderr, TGTFMT, "",
675 	    "(new writes prefer src.conf; env: SRC_ENV_CONF,");
676 	fprintf(stderr, TGTFMT, "",
677 	    "__MAKE_CONF, SRCCONF)");
678 	fprintf(stderr, TGTFMT, "rc",
679 	    "pass-through: all other arguments go to sysrc(8) verbatim");
680 	fprintf(stderr, TGTFMT, "generic",
681 	    "no default files; requires -f file");
682 	fprintf(stderr, "OPTIONS:\n");
683 #define OPTFMT "\t%-9s %s\n"
684 	fprintf(stderr, OPTFMT, "-A",
685 	    "Include the target's defaults file in the sourcing order");
686 	fprintf(stderr, OPTFMT, "",
687 	    "(alone, dumps everything, defaults included).");
688 	fprintf(stderr, OPTFMT, "-a",
689 	    "Dump the union of all directives from the target's files.");
690 	fprintf(stderr, OPTFMT, "-c",
691 	    "Check. Return success if no changes needed, else error.");
692 	fprintf(stderr, OPTFMT, "-D",
693 	    "Consult only the target's defaults file.");
694 	fprintf(stderr, OPTFMT, "-d",
695 	    "Show directive descriptions (from the defaults file's");
696 	fprintf(stderr, OPTFMT, "",
697 	    "comments; for sysctl, from the running kernel). With");
698 	fprintf(stderr, OPTFMT, "",
699 	    "-a, -A, or -D, describe every directive in scope.");
700 	fprintf(stderr, OPTFMT, "-E",
701 	    "With -l or -L, list only files that exist on disk.");
702 	fprintf(stderr, OPTFMT, "-e",
703 	    "Separate name and value with `=' (reads and write echoes).");
704 	fprintf(stderr, OPTFMT, "-F",
705 	    "Show the file holding each directive's effective value.");
706 	fprintf(stderr, OPTFMT, "-f file",
707 	    "Operate on file, in the target's format, instead of the");
708 	fprintf(stderr, OPTFMT, "",
709 	    "target's standard files (`-' means standard input).");
710 	fprintf(stderr, OPTFMT, "-h",
711 	    "Print a short usage statement to stderr and exit.");
712 	fprintf(stderr, OPTFMT, "--help",
713 	    "Print this message to stderr and exit.");
714 	fprintf(stderr, OPTFMT, "--version",
715 	    "Print the utility and library versions and exit.");
716 	fprintf(stderr, OPTFMT, "-i",
717 	    "Ignore unknown names (and quiet unknown sysctl OID warnings).");
718 	fprintf(stderr, OPTFMT, "-j jail",
719 	    "Operate within the jail `jail' (name or numeric id).");
720 	fprintf(stderr, OPTFMT, "-k module",
721 	    "Include the kernel module drop-in file for `module'.");
722 	fprintf(stderr, OPTFMT, "-l",
723 	    "List the pathnames of the target's backing files.");
724 	fprintf(stderr, OPTFMT, "-L",
725 	    "List all candidate files, including module drop-ins.");
726 	fprintf(stderr, OPTFMT, "-n",
727 	    "Show only directive values, not their names.");
728 	fprintf(stderr, OPTFMT, "-N",
729 	    "Show only directive names, not their values.");
730 	fprintf(stderr, OPTFMT, "-q",
731 	    "Quiet. Suppress unknown-directive warnings and the");
732 	fprintf(stderr, OPTFMT, "",
733 	    "`old -> new' echo of writes.");
734 	fprintf(stderr, OPTFMT, "-R dir",
735 	    "Operate within the root directory `dir' rather than `/'.");
736 	fprintf(stderr, OPTFMT, "-s",
737 	    "Set empty WITH_/WITHOUT_ knobs (make and src only).");
738 	fprintf(stderr, OPTFMT, "-v",
739 	    "Verbose. Print the pathname of the configuration file");
740 	fprintf(stderr, OPTFMT, "",
741 	    "holding the final effective value.");
742 	fprintf(stderr, OPTFMT, "-V",
743 	    "Trail. Reads: each assignment step (file:line, operator,");
744 	fprintf(stderr, OPTFMT, "",
745 	    "fragment, running effective). Writes: file:line:");
746 	fprintf(stderr, OPTFMT, "",
747 	    "name=old -> name=new (or name=value (unchanged)).");
748 	fprintf(stderr, OPTFMT, "-x",
749 	    "Remove name(s) from the target's files.");
750 	fprintf(stderr, "ENVIRONMENT:\n");
751 	fprintf(stderr, OPTFMT, "LOADER_DEFAULTS",
752 	    "Defaults file for the loader target (in place of");
753 	fprintf(stderr, OPTFMT, "",
754 	    "/boot/defaults/loader.conf).");
755 	fprintf(stderr, OPTFMT, "SRC_ENV_CONF",
756 	    "src-env.conf for the src target (default /etc/src-env.conf).");
757 	fprintf(stderr, OPTFMT, "__MAKE_CONF",
758 	    "make.conf for the src target (default /etc/make.conf).");
759 	fprintf(stderr, OPTFMT, "SRCCONF",
760 	    "src.conf for the src target (default /etc/src.conf).");
761 	exit(EXIT_FAILURE);
762 }
763