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 * Display helpers: name/value pairs, `-V' trails, write echoes.
10 */
11
12 #include <ctype.h>
13
14 #include "sysconf_priv.h"
15
16 /*
17 * True when `name' is a presence-only WITH_/WITHOUT_ build knob
18 * (share/mk/bsd.mkopt.mk tests defined(), and src.conf(5) ignores the
19 * value). WITHOUT_MODULES is excluded: its value is a module reject list.
20 */
21 int
make_knob_name_p(const char * name)22 make_knob_name_p(const char *name)
23 {
24
25 if (strcmp(name, "WITHOUT_MODULES") == 0)
26 return (0);
27 return (strncmp(name, "WITH_", 5) == 0 ||
28 strncmp(name, "WITHOUT_", 8) == 0);
29 }
30
31 /*
32 * Precursor to share/mk/bsd.mkopt.mk (seen under /usr/src after
33 * bsd.opts.mk / src.opts.mk, not bare make or ports): WITH_FOO=no
34 * does not disable FOO (presence still counts). Warn only for WITH_*
35 * with the exact value "no" (not WITHOUT_*, not "NO"). On reads,
36 * `path'/`line' locate the authoritative assignment when known.
37 */
38 void
warn_with_equals_no(const char * name,const char * value,const char * path,uint32_t line)39 warn_with_equals_no(const char *name, const char *value, const char *path,
40 uint32_t line)
41 {
42 const char *opt;
43
44 if (format != BSDCONF_FORMAT_MAKE && format != BSDCONF_FORMAT_SRC)
45 return;
46 if (name == NULL || value == NULL || strcmp(value, "no") != 0)
47 return;
48 /* WITH_ but not WITHOUT_ */
49 if (strncmp(name, "WITH_", 5) != 0 ||
50 strncmp(name, "WITHOUT_", 8) == 0)
51 return;
52 opt = name + 5;
53 if (path != NULL && line != 0)
54 warnx("%s:%u: Use WITHOUT_%s=1 instead of WITH_%s=no",
55 path, line, opt, opt);
56 else
57 warnx("Use WITHOUT_%s=1 instead of WITH_%s=no", opt, opt);
58 }
59
60 /*
61 * True when `name' is a presence-only WITH_/WITHOUT_ knob on make/src
62 * (bsd.mkopt.mk tests defined(); src.conf(5) ignores the value).
63 */
64 int
make_knob_p(const char * name)65 make_knob_p(const char *name)
66 {
67
68 if (format != BSDCONF_FORMAT_MAKE && format != BSDCONF_FORMAT_SRC)
69 return (0);
70 return (make_knob_name_p(name));
71 }
72
73 /*
74 * Print a directive/value pair according to the display flags. `path' is
75 * the configuration file holding the authoritative definition (or NULL).
76 */
77 void
print_pair(const char * path,const char * directive,const char * value)78 print_pair(const char *path, const char *directive, const char *value)
79 {
80 int knob;
81
82 if (show_file)
83 value = path != NULL ? path : "(none)";
84 if (verbose && path != NULL && !show_file)
85 printf("%s: ", path);
86 if (name_only) {
87 puts(directive);
88 return;
89 }
90 /* Presence knobs: any assigned value means present (defined()). */
91 knob = !show_file && make_knob_p(directive);
92 if (value_only) {
93 puts(value != NULL ? value : "");
94 return;
95 }
96 /*
97 * Presence display is for the human-readable form only. With -e,
98 * emit the real assignment (`NAME=') so the spelling is valid make
99 * and matches what -s writes.
100 */
101 if (knob && !show_equals) {
102 printf("%s (present)\n", directive);
103 return;
104 }
105 if (show_equals && !show_file) {
106 if ((bsdconf_format_put(format) &
107 BSDCONF_PUT_QUOTE_ALWAYS) != 0)
108 printf("%s=\"%s\"\n", directive,
109 value != NULL ? value : "");
110 else
111 printf("%s=%s\n", directive,
112 value != NULL ? value : "");
113 return;
114 }
115 printf("%s: %s\n", directive, value != NULL ? value : "");
116 }
117
118 /*
119 * Spelling of a make(1) (or plain) assignment operator for `-V' trails.
120 */
121 const char *
op_str(enum bsdconf_op op)122 op_str(enum bsdconf_op op)
123 {
124
125 switch (op) {
126 case BSDCONF_OP_APPEND:
127 return ("+=");
128 case BSDCONF_OP_COND:
129 return ("?=");
130 case BSDCONF_OP_EXPAND:
131 return (":=");
132 case BSDCONF_OP_SHELL:
133 return ("!=");
134 case BSDCONF_OP_ASSIGN:
135 case BSDCONF_OP_DEFAULT:
136 default:
137 return ("=");
138 }
139 }
140
141 /*
142 * Append one assignment step to a `-V' trail. No-op when `-V' is unset.
143 * Returns zero on success; -1 (errno set) on allocation failure.
144 */
145 int
trail_push(struct trail_step ** trailp,size_t * ntrailp,size_t fileidx,uint32_t line,enum bsdconf_op op,const char * piece,const char * effective)146 trail_push(struct trail_step **trailp, size_t *ntrailp,
147 size_t fileidx, uint32_t line, enum bsdconf_op op,
148 const char *piece, const char *effective)
149 {
150 struct trail_step *nt;
151 char *ecopy;
152 char *pcopy;
153
154 if (!verbose_trail)
155 return (0);
156 if (piece == NULL)
157 piece = "";
158 if (effective == NULL)
159 effective = "";
160 if ((pcopy = strdup(piece)) == NULL)
161 return (-1);
162 if ((ecopy = strdup(effective)) == NULL) {
163 free(pcopy);
164 return (-1);
165 }
166 nt = realloc(*trailp, (*ntrailp + 1) * sizeof(*nt));
167 if (nt == NULL) {
168 free(pcopy);
169 free(ecopy);
170 return (-1);
171 }
172 *trailp = nt;
173 nt[*ntrailp].fileidx = fileidx;
174 nt[*ntrailp].line = line;
175 nt[*ntrailp].op = op;
176 nt[*ntrailp].piece = pcopy;
177 nt[*ntrailp].effective = ecopy;
178 (*ntrailp)++;
179 return (0);
180 }
181
182 void
trail_free(struct trail_step * trail,size_t ntrail)183 trail_free(struct trail_step *trail, size_t ntrail)
184 {
185 size_t i;
186
187 for (i = 0; i < ntrail; i++) {
188 free(trail[i].piece);
189 free(trail[i].effective);
190 }
191 free(trail);
192 }
193
194 /*
195 * Print every recorded assignment step for `name'. Each line is
196 * `file:line: nameoppiece' and, when the running effective value differs
197 * from the fragment, ` -> effective'.
198 */
199 void
print_trail(const char * name,const struct trail_step * trail,size_t ntrail)200 print_trail(const char *name, const struct trail_step *trail, size_t ntrail)
201 {
202 const char *path;
203 size_t i;
204
205 for (i = 0; i < ntrail; i++) {
206 path = conf_files[trail[i].fileidx];
207 if (strcmp(trail[i].piece, trail[i].effective) != 0)
208 printf("%s:%u: %s%s%s -> %s\n", path, trail[i].line,
209 name, op_str(trail[i].op), trail[i].piece,
210 trail[i].effective);
211 else
212 printf("%s:%u: %s%s%s\n", path, trail[i].line, name,
213 op_str(trail[i].op), trail[i].piece);
214 }
215 }
216
217 /*
218 * Echo a write result. With -V: `file:line: name=old -> name=new',
219 * `name=value (unchanged)', or `name=value (added)', matching read-trail
220 * shape. With -v alone: prefix the pathname. Otherwise: `name: old -> new'
221 * / `name: value (unchanged)' (additions still use old -> new with an
222 * empty old value). With `-e', the non-trail form uses `=' (and quotes
223 * for formats that always quote), matching print_pair() on reads --
224 * except make/src WITH_/WITHOUT_ presence knobs, which use
225 * `(not present) -> (present)' when newly defined,
226 * `(present) (unchanged)' when `-s' finds them already defined or
227 * an explicit write leaves the value unchanged, and a normal `old ->
228 * new' echo when the placeholder value changes while still present
229 * (also for `-e'). Removals are only echoed when the caller passes
230 * `-v'/`-V' (not bare `-x'), using the same `name (removed)' / trail
231 * form as other directives.
232 */
233 void
print_write_echo(const char * path,uint32_t line,const char * name,enum bsdconf_op op,const char * oldv,const char * newv,int changed,int added,int removed)234 print_write_echo(const char *path, uint32_t line, const char *name,
235 enum bsdconf_op op, const char *oldv, const char *newv, int changed,
236 int added, int removed)
237 {
238 const char *ops;
239 const char *shown;
240 int knob;
241 int quote;
242 int was_present;
243
244 /* NULL oldv: absent; "" : present with empty assignment */
245 was_present = (oldv != NULL);
246 if (oldv == NULL)
247 oldv = "";
248 if (newv == NULL)
249 newv = "";
250 ops = op_str(op);
251 shown = oldv[0] != '\0' ? oldv : newv;
252 knob = make_knob_p(name) && op != BSDCONF_OP_APPEND;
253
254 if (verbose_trail && path != NULL) {
255 if (removed) {
256 printf("%s:%u: %s (removed)\n", path, line, name);
257 return;
258 }
259 if (knob) {
260 if (!was_present || added) {
261 printf("%s:%u: %s: (not present) -> "
262 "(present)\n", path, line, name);
263 return;
264 }
265 if (strcmp(oldv, newv) == 0) {
266 printf("%s:%u: %s: (present) (unchanged)\n",
267 path, line, name);
268 return;
269 }
270 /* value change while present: fall through */
271 }
272 if (added)
273 printf("%s:%u: %s%s%s (added)\n", path, line, name,
274 ops, newv);
275 else if (changed)
276 printf("%s:%u: %s%s%s -> %s%s%s\n", path, line, name,
277 ops, oldv, name, ops, newv);
278 else
279 printf("%s:%u: %s%s%s (unchanged)\n", path, line, name,
280 ops, shown);
281 return;
282 }
283 if (verbose && path != NULL)
284 printf("%s: ", path);
285 if (removed) {
286 printf("%s (removed)\n", name);
287 return;
288 }
289 if (knob) {
290 if (!was_present || added) {
291 printf("%s: (not present) -> (present)\n", name);
292 return;
293 }
294 if (strcmp(oldv, newv) == 0) {
295 printf("%s: (present) (unchanged)\n", name);
296 return;
297 }
298 /* value change while present: fall through */
299 }
300 quote = show_equals && !show_file &&
301 (bsdconf_format_put(format) & BSDCONF_PUT_QUOTE_ALWAYS) != 0;
302 if (show_equals && !show_file) {
303 if (changed || added) {
304 /* sysrc(8)-style: `name=old # -> new' with `-e' */
305 if (quote)
306 printf("%s=\"%s\" # -> \"%s\"\n", name, oldv,
307 newv);
308 else
309 printf("%s=%s # -> %s\n", name, oldv, newv);
310 } else if (quote)
311 printf("%s=\"%s\" (unchanged)\n", name, shown);
312 else
313 printf("%s=%s (unchanged)\n", name, shown);
314 return;
315 }
316 if (changed || added)
317 printf("%s: %s -> %s\n", name, oldv, newv);
318 else
319 printf("%s: %s (unchanged)\n", name, shown);
320 }
321