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