1*3fe5961aSDevin Teske /*
2*3fe5961aSDevin Teske * Copyright (c) 2013-2026 Devin Teske <dteske@FreeBSD.org>
3*3fe5961aSDevin Teske * Copyright (c) 2021-2026 Faraz Vahedi <kfv@FreeBSD.org>
4*3fe5961aSDevin Teske *
5*3fe5961aSDevin Teske * SPDX-License-Identifier: BSD-2-Clause
6*3fe5961aSDevin Teske */
7*3fe5961aSDevin Teske
8*3fe5961aSDevin Teske /*
9*3fe5961aSDevin Teske * Apply or check assignments against conf files (bsdconf_put path).
10*3fe5961aSDevin Teske */
11*3fe5961aSDevin Teske
12*3fe5961aSDevin Teske #include <ctype.h>
13*3fe5961aSDevin Teske
14*3fe5961aSDevin Teske #include "sysconf_priv.h"
15*3fe5961aSDevin Teske
16*3fe5961aSDevin Teske /*
17*3fe5961aSDevin Teske * Process `-c' from the results of a completed scan_pass() without touching
18*3fe5961aSDevin Teske * any file: a write request differs when the authoritative value does not
19*3fe5961aSDevin Teske * match, a make(1) `+=value' request differs when no identical `+=value'
20*3fe5961aSDevin Teske * line is already present, and a removal request differs when the
21*3fe5961aSDevin Teske * directive is still present. Returns EXIT_SUCCESS when no changes would
22*3fe5961aSDevin Teske * be required.
23*3fe5961aSDevin Teske */
24*3fe5961aSDevin Teske int
do_checks(void)25*3fe5961aSDevin Teske do_checks(void)
26*3fe5961aSDevin Teske {
27*3fe5961aSDevin Teske int differs;
28*3fe5961aSDevin Teske int rv = EXIT_SUCCESS;
29*3fe5961aSDevin Teske unsigned int n;
30*3fe5961aSDevin Teske
31*3fe5961aSDevin Teske for (n = 0; n < nreqs; n++) {
32*3fe5961aSDevin Teske if (reqs[n].newvalue == NULL && !reqs[n].remove)
33*3fe5961aSDevin Teske continue;
34*3fe5961aSDevin Teske if (reqs[n].remove)
35*3fe5961aSDevin Teske differs = reqs[n].found;
36*3fe5961aSDevin Teske else if (reqs[n].op == BSDCONF_OP_APPEND)
37*3fe5961aSDevin Teske differs = !reqs[n].append_present;
38*3fe5961aSDevin Teske else
39*3fe5961aSDevin Teske differs = !reqs[n].found ||
40*3fe5961aSDevin Teske strcmp(reqs[n].value, reqs[n].newvalue) != 0;
41*3fe5961aSDevin Teske if (differs) {
42*3fe5961aSDevin Teske if (!quiet)
43*3fe5961aSDevin Teske warnx("%s: value differs", reqs[n].name);
44*3fe5961aSDevin Teske rv = EXIT_FAILURE;
45*3fe5961aSDevin Teske }
46*3fe5961aSDevin Teske }
47*3fe5961aSDevin Teske
48*3fe5961aSDevin Teske return (rv);
49*3fe5961aSDevin Teske }
50*3fe5961aSDevin Teske
51*3fe5961aSDevin Teske /*
52*3fe5961aSDevin Teske * Process write and remove requests using the srcidx/infile maps recorded
53*3fe5961aSDevin Teske * by scan_pass(). A directive is rewritten in the file that authoritatively
54*3fe5961aSDevin Teske * defines it (the last file in sourcing order that lists it) so the change
55*3fe5961aSDevin Teske * survives a reboot; a directive defined nowhere is appended to the
56*3fe5961aSDevin Teske * format's default file. Removals are applied to every file listing the
57*3fe5961aSDevin Teske * directive, lest an earlier definition be unmasked. Each modified file is
58*3fe5961aSDevin Teske * replaced in one atomic bsdconf_put() transaction. Returns EXIT_SUCCESS
59*3fe5961aSDevin Teske * or EXIT_FAILURE.
60*3fe5961aSDevin Teske */
61*3fe5961aSDevin Teske int
do_writes(void)62*3fe5961aSDevin Teske do_writes(void)
63*3fe5961aSDevin Teske {
64*3fe5961aSDevin Teske int fd;
65*3fe5961aSDevin Teske int rv = EXIT_SUCCESS;
66*3fe5961aSDevin Teske size_t f;
67*3fe5961aSDevin Teske unsigned int n;
68*3fe5961aSDevin Teske unsigned int nopts;
69*3fe5961aSDevin Teske struct bsdconf_option *opt;
70*3fe5961aSDevin Teske struct bsdconf_option *options;
71*3fe5961aSDevin Teske struct request **owner;
72*3fe5961aSDevin Teske
73*3fe5961aSDevin Teske if ((options = calloc(nreqs + 1, sizeof(*options))) == NULL ||
74*3fe5961aSDevin Teske (owner = calloc(nreqs, sizeof(*owner))) == NULL)
75*3fe5961aSDevin Teske err(EXIT_FAILURE, NULL);
76*3fe5961aSDevin Teske
77*3fe5961aSDevin Teske for (f = 0; f < nconf_files; f++) {
78*3fe5961aSDevin Teske /* Collect the requests this file is responsible for */
79*3fe5961aSDevin Teske nopts = 0;
80*3fe5961aSDevin Teske for (n = 0; n < nreqs; n++) {
81*3fe5961aSDevin Teske size_t target;
82*3fe5961aSDevin Teske
83*3fe5961aSDevin Teske if (reqs[n].remove) {
84*3fe5961aSDevin Teske if (!reqs[n].infile[f])
85*3fe5961aSDevin Teske continue;
86*3fe5961aSDevin Teske } else if (reqs[n].newvalue != NULL) {
87*3fe5961aSDevin Teske /*
88*3fe5961aSDevin Teske * A directive whose only definition is
89*3fe5961aSDevin Teske * the read-only defaults file is written
90*3fe5961aSDevin Teske * like one defined nowhere: appended to
91*3fe5961aSDevin Teske * the format's default write target.
92*3fe5961aSDevin Teske */
93*3fe5961aSDevin Teske if (reqs[n].srcidx < 0 ||
94*3fe5961aSDevin Teske reqs[n].srcidx == defaults_idx)
95*3fe5961aSDevin Teske target = write_idx;
96*3fe5961aSDevin Teske else
97*3fe5961aSDevin Teske target = (size_t)reqs[n].srcidx;
98*3fe5961aSDevin Teske if (target != f)
99*3fe5961aSDevin Teske continue;
100*3fe5961aSDevin Teske /*
101*3fe5961aSDevin Teske * An identical make(1) `+=value' line is
102*3fe5961aSDevin Teske * already present: nothing to write, but
103*3fe5961aSDevin Teske * still report the effective value unless
104*3fe5961aSDevin Teske * -q (see report loop for the SET case).
105*3fe5961aSDevin Teske */
106*3fe5961aSDevin Teske if (reqs[n].op == BSDCONF_OP_APPEND &&
107*3fe5961aSDevin Teske reqs[n].append_present) {
108*3fe5961aSDevin Teske if (!quiet)
109*3fe5961aSDevin Teske print_write_echo(conf_files[f],
110*3fe5961aSDevin Teske 0, reqs[n].name,
111*3fe5961aSDevin Teske BSDCONF_OP_APPEND,
112*3fe5961aSDevin Teske reqs[n].value,
113*3fe5961aSDevin Teske reqs[n].value, 0, 0, 0);
114*3fe5961aSDevin Teske continue;
115*3fe5961aSDevin Teske }
116*3fe5961aSDevin Teske /*
117*3fe5961aSDevin Teske * -s only ensures presence: if the
118*3fe5961aSDevin Teske * WITH_/WITHOUT_ knob is already defined
119*3fe5961aSDevin Teske * (any value), leave the file alone.
120*3fe5961aSDevin Teske * An explicit name= (including empty)
121*3fe5961aSDevin Teske * always writes the requested value.
122*3fe5961aSDevin Teske */
123*3fe5961aSDevin Teske if (set_knobs && make_knob_p(reqs[n].name) &&
124*3fe5961aSDevin Teske reqs[n].found) {
125*3fe5961aSDevin Teske if (!quiet)
126*3fe5961aSDevin Teske print_write_echo(conf_files[f],
127*3fe5961aSDevin Teske 0, reqs[n].name,
128*3fe5961aSDevin Teske reqs[n].op,
129*3fe5961aSDevin Teske reqs[n].value,
130*3fe5961aSDevin Teske reqs[n].value, 0, 0,
131*3fe5961aSDevin Teske 0);
132*3fe5961aSDevin Teske continue;
133*3fe5961aSDevin Teske }
134*3fe5961aSDevin Teske } else
135*3fe5961aSDevin Teske continue;
136*3fe5961aSDevin Teske opt = &options[nopts];
137*3fe5961aSDevin Teske memset(opt, 0, sizeof(*opt));
138*3fe5961aSDevin Teske opt->type = BSDCONF_TYPE_STR;
139*3fe5961aSDevin Teske opt->directive = reqs[n].name;
140*3fe5961aSDevin Teske opt->op = reqs[n].op;
141*3fe5961aSDevin Teske if (reqs[n].remove) {
142*3fe5961aSDevin Teske opt->action = BSDCONF_ACTION_REMOVE;
143*3fe5961aSDevin Teske opt->value.str = NULL;
144*3fe5961aSDevin Teske } else {
145*3fe5961aSDevin Teske opt->action = BSDCONF_ACTION_SET_VALUE;
146*3fe5961aSDevin Teske opt->value.str =
147*3fe5961aSDevin Teske (char *)(uintptr_t)reqs[n].newvalue;
148*3fe5961aSDevin Teske }
149*3fe5961aSDevin Teske owner[nopts++] = &reqs[n];
150*3fe5961aSDevin Teske }
151*3fe5961aSDevin Teske if (nopts == 0)
152*3fe5961aSDevin Teske continue;
153*3fe5961aSDevin Teske memset(&options[nopts], 0, sizeof(options[nopts]));
154*3fe5961aSDevin Teske
155*3fe5961aSDevin Teske /*
156*3fe5961aSDevin Teske * Create the target if it does not yet exist. O_EXCL makes
157*3fe5961aSDevin Teske * the test-and-create atomic: an existing file (or one that
158*3fe5961aSDevin Teske * appears concurrently) is left untouched.
159*3fe5961aSDevin Teske */
160*3fe5961aSDevin Teske if ((fd = open(conf_files[f], O_WRONLY | O_CREAT | O_EXCL,
161*3fe5961aSDevin Teske 0666)) >= 0)
162*3fe5961aSDevin Teske close(fd);
163*3fe5961aSDevin Teske else if (errno != EEXIST)
164*3fe5961aSDevin Teske err(EXIT_FAILURE, "%s", conf_files[f]);
165*3fe5961aSDevin Teske
166*3fe5961aSDevin Teske if (bsdconf_put(options, conf_files[f],
167*3fe5961aSDevin Teske bsdconf_format_processing(format),
168*3fe5961aSDevin Teske bsdconf_format_put(format)) != 0)
169*3fe5961aSDevin Teske err(EXIT_FAILURE, "%s", conf_files[f]);
170*3fe5961aSDevin Teske
171*3fe5961aSDevin Teske /* Report the results */
172*3fe5961aSDevin Teske for (n = 0; n < nopts; n++) {
173*3fe5961aSDevin Teske opt = &options[n];
174*3fe5961aSDevin Teske if (opt->action == BSDCONF_ACTION_REMOVE) {
175*3fe5961aSDevin Teske if ((opt->result &
176*3fe5961aSDevin Teske BSDCONF_DIRECTIVE_REMOVED) != 0) {
177*3fe5961aSDevin Teske owner[n]->found = 1;
178*3fe5961aSDevin Teske /*
179*3fe5961aSDevin Teske * Removals are quiet unless -v/-V
180*3fe5961aSDevin Teske * (or -q which is already silent).
181*3fe5961aSDevin Teske */
182*3fe5961aSDevin Teske if (!quiet &&
183*3fe5961aSDevin Teske (verbose || verbose_trail))
184*3fe5961aSDevin Teske print_write_echo(
185*3fe5961aSDevin Teske conf_files[f],
186*3fe5961aSDevin Teske opt->line,
187*3fe5961aSDevin Teske opt->directive,
188*3fe5961aSDevin Teske opt->op, "", "", 0, 0,
189*3fe5961aSDevin Teske 1);
190*3fe5961aSDevin Teske }
191*3fe5961aSDevin Teske continue;
192*3fe5961aSDevin Teske }
193*3fe5961aSDevin Teske if (!quiet) {
194*3fe5961aSDevin Teske const char *old = owner[n]->value;
195*3fe5961aSDevin Teske char *neweff = NULL;
196*3fe5961aSDevin Teske const char *shown;
197*3fe5961aSDevin Teske
198*3fe5961aSDevin Teske /*
199*3fe5961aSDevin Teske * Gate the echo on whether bsdconf_put()
200*3fe5961aSDevin Teske * actually edited the file
201*3fe5961aSDevin Teske * (BSDCONF_VALUE_CHANGED). With -V, shape the
202*3fe5961aSDevin Teske * echo like a read trail
203*3fe5961aSDevin Teske * (`file:line: name=old -> name=new'). For
204*3fe5961aSDevin Teske * make(1) `+=', show the effective value
205*3fe5961aSDevin Teske * before and after the append rather than
206*3fe5961aSDevin Teske * the fragment alone.
207*3fe5961aSDevin Teske */
208*3fe5961aSDevin Teske if (opt->op == BSDCONF_OP_APPEND) {
209*3fe5961aSDevin Teske neweff = make_apply(BSDCONF_OP_APPEND,
210*3fe5961aSDevin Teske old, opt->value.str);
211*3fe5961aSDevin Teske if (neweff == NULL)
212*3fe5961aSDevin Teske err(EXIT_FAILURE, NULL);
213*3fe5961aSDevin Teske shown = neweff;
214*3fe5961aSDevin Teske } else
215*3fe5961aSDevin Teske shown = opt->value.str;
216*3fe5961aSDevin Teske /*
217*3fe5961aSDevin Teske * -V shows statement shape at file:line; a
218*3fe5961aSDevin Teske * newly added statement is `(added)' rather
219*3fe5961aSDevin Teske * than `op -> op+value'. -v keeps effective
220*3fe5961aSDevin Teske * old -> new for `+='.
221*3fe5961aSDevin Teske */
222*3fe5961aSDevin Teske if (verbose_trail &&
223*3fe5961aSDevin Teske (opt->result &
224*3fe5961aSDevin Teske BSDCONF_DIRECTIVE_ADDED) != 0)
225*3fe5961aSDevin Teske print_write_echo(conf_files[f],
226*3fe5961aSDevin Teske opt->line, opt->directive, opt->op,
227*3fe5961aSDevin Teske "", opt->value.str,
228*3fe5961aSDevin Teske (opt->result &
229*3fe5961aSDevin Teske BSDCONF_VALUE_CHANGED) != 0, 1, 0);
230*3fe5961aSDevin Teske else if (verbose_trail)
231*3fe5961aSDevin Teske print_write_echo(conf_files[f],
232*3fe5961aSDevin Teske opt->line, opt->directive, opt->op,
233*3fe5961aSDevin Teske old == NULL ? "" : old, shown,
234*3fe5961aSDevin Teske (opt->result &
235*3fe5961aSDevin Teske BSDCONF_VALUE_CHANGED) != 0, 0, 0);
236*3fe5961aSDevin Teske else
237*3fe5961aSDevin Teske print_write_echo(conf_files[f],
238*3fe5961aSDevin Teske opt->line, opt->directive, opt->op,
239*3fe5961aSDevin Teske old, shown,
240*3fe5961aSDevin Teske (opt->result &
241*3fe5961aSDevin Teske BSDCONF_VALUE_CHANGED) != 0, 0, 0);
242*3fe5961aSDevin Teske if (neweff != NULL) {
243*3fe5961aSDevin Teske free(owner[n]->value);
244*3fe5961aSDevin Teske owner[n]->value = neweff;
245*3fe5961aSDevin Teske }
246*3fe5961aSDevin Teske }
247*3fe5961aSDevin Teske }
248*3fe5961aSDevin Teske }
249*3fe5961aSDevin Teske
250*3fe5961aSDevin Teske /* A removal that matched no file at all is an unknown directive */
251*3fe5961aSDevin Teske for (n = 0; n < nreqs; n++) {
252*3fe5961aSDevin Teske if (!reqs[n].remove || reqs[n].found)
253*3fe5961aSDevin Teske continue;
254*3fe5961aSDevin Teske if (ignore_unknown)
255*3fe5961aSDevin Teske continue;
256*3fe5961aSDevin Teske if (!quiet)
257*3fe5961aSDevin Teske warnx("unknown directive '%s'", reqs[n].name);
258*3fe5961aSDevin Teske rv = EXIT_FAILURE;
259*3fe5961aSDevin Teske }
260*3fe5961aSDevin Teske
261*3fe5961aSDevin Teske free(options);
262*3fe5961aSDevin Teske free(owner);
263*3fe5961aSDevin Teske return (rv);
264*3fe5961aSDevin Teske }
265