xref: /freebsd/usr.sbin/sysconf/sysconf_edit_write.c (revision 3fe5961a0b708da599d42cbb6b5e4f030c28ea45)
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