xref: /freebsd/usr.sbin/sysconf/sysconf_query_desc.c (revision 3fe5961a0b708da599d42cbb6b5e4f030c28ea45)
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  * Directive descriptions harvested from defaults-file comments (-d).
10  */
11 
12 #include "sysconf_priv.h"
13 
14 /*
15  * Directive descriptions harvested from the defaults file by
16  * load_descriptions() below.
17  */
18 struct descent {
19 	char		*name;		/* directive name */
20 	char		*desc;		/* description (may be empty) */
21 };
22 
23 static struct descent	*descs;		/* set by load_descriptions() */
24 static size_t		ndescs;
25 static size_t		descsize;
26 
27 /*
28  * Harvest every directive description from the defaults file, where a
29  * description is the inline comment trailing the directive's default
30  * assignment, continued across any subsequent lines of whitespace
31  * followed by a comment character. A commented-out default
32  * (`#comconsole_speed="115200" # Set the ...') yields its directive and
33  * description all the same -- deliberately unlike sysrc(8), which goes
34  * blind when the default itself is disabled. Comment prose is never
35  * mistaken for a directive: a name must directly abut the optional
36  * leading `#' and run unbroken to its `='.
37  */
38 void
load_descriptions(void)39 load_descriptions(void)
40 {
41 	int current = -1;	/* entry whose description is growing */
42 	FILE *fp;
43 	ssize_t linelen;
44 	size_t len;
45 	size_t linesize = 0;
46 	size_t n;
47 	char *defaults;
48 	char *line = NULL;
49 	char *p;
50 	char *v;
51 	struct descent *tmp;
52 
53 	if ((defaults = defaults_path()) == NULL)
54 		errx(EXIT_FAILURE, "target has no defaults file");
55 	if ((fp = fopen(defaults, "r")) == NULL)
56 		err(EXIT_FAILURE, "%s", defaults);
57 
58 	while ((linelen = getline(&line, &linesize, fp)) != -1) {
59 		int quoted = 0;
60 
61 		if (linelen > 0 && line[linelen - 1] == '\n')
62 			line[--linelen] = '\0';
63 
64 		/* Whitespace then `#' continues the open description */
65 		p = line;
66 		if (*p == ' ' || *p == '\t') {
67 			p += strspn(p, " \t");
68 			if (*p == '#' && current >= 0) {
69 				char *grown;
70 
71 				p++;
72 				p += strspn(p, " \t");
73 				if (*p == '\0')
74 					continue;
75 				len = strlen(descs[current].desc) +
76 				    strlen(p) + 2;
77 				if ((grown = malloc(len)) == NULL)
78 					err(EXIT_FAILURE, NULL);
79 				snprintf(grown, len, "%s%s%s",
80 				    descs[current].desc,
81 				    *descs[current].desc != '\0' ? " " : "",
82 				    p);
83 				free(descs[current].desc);
84 				descs[current].desc = grown;
85 				continue;
86 			}
87 			current = -1;
88 			continue;
89 		}
90 		current = -1;
91 
92 		/* A commented-out default still describes its directive */
93 		if (*p == '#')
94 			p++;
95 
96 		/* A directive name runs unbroken to its `=' */
97 		len = strcspn(p, " \t\"#=");
98 		if (len == 0 || p[len] != '=')
99 			continue;
100 
101 		/* First description wins on a repeated directive */
102 		for (n = 0; n < ndescs; n++)
103 			if (strncmp(descs[n].name, p, len) == 0 &&
104 			    descs[n].name[len] == '\0')
105 				break;
106 		if (n < ndescs)
107 			continue;
108 
109 		/* Skip the value (quotes may hide a `#') */
110 		for (v = p + len + 1; *v != '\0'; v++) {
111 			if (*v == '"')
112 				quoted = !quoted;
113 			else if (*v == '#' && !quoted)
114 				break;
115 		}
116 		if (*v == '#') {
117 			v++;
118 			v += strspn(v, " \t");
119 		}
120 
121 		if (ndescs >= descsize) {
122 			descsize = (descsize == 0) ? 64 : descsize << 1;
123 			tmp = realloc(descs, descsize * sizeof(*descs));
124 			if (tmp == NULL)
125 				err(EXIT_FAILURE, NULL);
126 			descs = tmp;
127 		}
128 		descs[ndescs].name = strndup(p, len);
129 		descs[ndescs].desc = strdup(v);
130 		if (descs[ndescs].name == NULL ||
131 		    descs[ndescs].desc == NULL)
132 			err(EXIT_FAILURE, NULL);
133 		current = (int)ndescs;
134 		ndescs++;
135 	}
136 	free(line);
137 	fclose(fp);
138 	free(defaults);
139 }
140 
141 /*
142  * Return the harvested description of `name' or NULL if the defaults file
143  * does not mention it.
144  */
145 static const char *
desc_find(const char * name)146 desc_find(const char *name)
147 {
148 	size_t n;
149 
150 	for (n = 0; n < ndescs; n++)
151 		if (strcmp(descs[n].name, name) == 0)
152 			return (descs[n].desc);
153 	return (NULL);
154 }
155 
156 /*
157  * Print the description of `directive' according to the display flags: for
158  * the sysctl target, from the running kernel; otherwise from the table
159  * harvested off the defaults file (a directive the defaults never mention
160  * prints an empty description).
161  */
162 void
print_desc(const char * directive)163 print_desc(const char *directive)
164 {
165 	const char *desc = NULL;
166 #if defined(__FreeBSD__)
167 	char buf[BUFSIZ];
168 
169 	if (format == BSDCONF_FORMAT_SYSCTL) {
170 		(void)sysctl_descr(directive, buf, sizeof(buf));
171 		desc = buf;
172 	}
173 #endif
174 	if (desc == NULL && (desc = desc_find(directive)) == NULL)
175 		desc = "";
176 	if (name_only)
177 		puts(directive);
178 	else if (value_only)
179 		puts(desc);
180 	else
181 		printf("%s: %s\n", directive, desc);
182 }
183 
184 /*
185  * Process `-d' with explicit names for a defaults-backed target. Returns
186  * EXIT_SUCCESS or EXIT_FAILURE.
187  */
188 int
describe_defaults(void)189 describe_defaults(void)
190 {
191 	int rv = EXIT_SUCCESS;
192 	unsigned int n;
193 
194 	load_descriptions();
195 	for (n = 0; n < nreqs; n++) {
196 		if (desc_find(reqs[n].name) == NULL) {
197 			if (ignore_unknown)
198 				continue;
199 			if (!quiet)
200 				warnx("unknown directive '%s'",
201 				    reqs[n].name);
202 			rv = EXIT_FAILURE;
203 			continue;
204 		}
205 		print_desc(reqs[n].name);
206 	}
207 
208 	return (rv);
209 }
210