xref: /freebsd/usr.sbin/sysconf/sysconf_edit_make.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  * make(1)/src triad word-removal across += assignment fragments.
10  */
11 
12 #include <ctype.h>
13 
14 #include "sysconf_priv.h"
15 
16 int
make_strike_p(const struct request * req)17 make_strike_p(const struct request *req)
18 {
19 
20 	return ((bsdconf_format_processing(format) &
21 	    BSDCONF_OPERATOR_EQUALS) != 0 &&
22 	    req->listop == '-' && req->newvalue != NULL);
23 }
24 
25 int
make_assign_push(struct request * req,enum bsdconf_op op,const char * piece,uint32_t line)26 make_assign_push(struct request *req, enum bsdconf_op op,
27     const char *piece, uint32_t line)
28 {
29 	struct make_assign *na;
30 	char *pcopy;
31 
32 	if (piece == NULL)
33 		piece = "";
34 	if ((pcopy = strdup(piece)) == NULL)
35 		return (-1);
36 	na = realloc(req->assigns, (req->nassigns + 1) * sizeof(*na));
37 	if (na == NULL) {
38 		free(pcopy);
39 		return (-1);
40 	}
41 	req->assigns = na;
42 	na[req->nassigns].fileidx = conf_scanidx;
43 	na[req->nassigns].line = line;
44 	na[req->nassigns].op = op;
45 	na[req->nassigns].piece = pcopy;
46 	req->nassigns++;
47 	return (0);
48 }
49 
50 /*
51  * Apply make/src `name-=word' strikes: for each whitespace-separated word,
52  * find the last assignment whose piece contains it and remove the word from
53  * that piece (delete the statement if the piece becomes empty). With
54  * `check_only', report whether any change would be required without writing.
55  * Echoes effective old/new (or `(unchanged)') unless -q. Neutralizes each
56  * handled request so do_writes() skips it. Returns EXIT_SUCCESS or
57  * EXIT_FAILURE.
58  */
59 int
do_make_strikes(int check_only)60 do_make_strikes(int check_only)
61 {
62 	char **work;
63 	char *new_eff;
64 	char *token;
65 	char *wordscopy;
66 	char *wp;
67 	const char *old_eff;
68 	int differs;
69 	int rv = EXIT_SUCCESS;
70 	size_t i;
71 	size_t j;
72 	size_t wlen;
73 	unsigned int n;
74 
75 	for (n = 0; n < nreqs; n++) {
76 		if (!make_strike_p(&reqs[n]))
77 			continue;
78 		if (!reqs[n].found || reqs[n].nassigns == 0) {
79 			if (!ignore_unknown) {
80 				if (!quiet)
81 					warnx("unknown directive '%s'",
82 					    reqs[n].name);
83 				rv = EXIT_FAILURE;
84 			}
85 			reqs[n].newvalue = NULL;
86 			reqs[n].name = "";
87 			continue;
88 		}
89 
90 		if ((work = calloc(reqs[n].nassigns, sizeof(*work))) == NULL)
91 			err(EXIT_FAILURE, NULL);
92 		for (i = 0; i < reqs[n].nassigns; i++) {
93 			work[i] = strdup(reqs[n].assigns[i].piece);
94 			if (work[i] == NULL)
95 				err(EXIT_FAILURE, NULL);
96 		}
97 
98 		differs = 0;
99 		if ((wordscopy = strdup(reqs[n].newvalue)) == NULL)
100 			err(EXIT_FAILURE, NULL);
101 		wp = wordscopy;
102 		while ((token = strsep(&wp, " \t")) != NULL) {
103 			if (*token == '\0')
104 				continue;
105 			wlen = strlen(token);
106 			for (i = reqs[n].nassigns; i > 0; i--) {
107 				j = i - 1;
108 				if (!list_has(work[j], token, wlen))
109 					continue;
110 				{
111 					char *merged;
112 
113 					merged = list_merge(work[j], token,
114 					    '-');
115 					free(work[j]);
116 					work[j] = merged;
117 					differs = 1;
118 				}
119 				break;
120 			}
121 		}
122 		free(wordscopy);
123 
124 		if (check_only) {
125 			if (differs) {
126 				if (!quiet)
127 					warnx("%s: value differs",
128 					    reqs[n].name);
129 				rv = EXIT_FAILURE;
130 			}
131 			goto neutralize;
132 		}
133 
134 		old_eff = reqs[n].value != NULL ? reqs[n].value : "";
135 		if (!differs) {
136 			if (!quiet)
137 				print_write_echo(
138 				    reqs[n].srcidx >= 0 ?
139 				    conf_files[reqs[n].srcidx] : NULL,
140 				    0, reqs[n].name, BSDCONF_OP_ASSIGN,
141 				    old_eff, old_eff, 0, 0, 0);
142 			goto neutralize;
143 		}
144 
145 		/* Effective value after applying the struck pieces */
146 		new_eff = NULL;
147 		for (i = 0; i < reqs[n].nassigns; i++) {
148 			char *merged;
149 
150 			if (work[i][0] == '\0')
151 				continue;
152 			merged = make_apply(reqs[n].assigns[i].op, new_eff,
153 			    work[i]);
154 			if (merged == NULL)
155 				err(EXIT_FAILURE, NULL);
156 			free(new_eff);
157 			new_eff = merged;
158 		}
159 		if (new_eff == NULL) {
160 			if ((new_eff = strdup("")) == NULL)
161 				err(EXIT_FAILURE, NULL);
162 		}
163 
164 		/* Put each changed statement (line-targeted) */
165 		for (i = 0; i < nconf_files; i++) {
166 			struct bsdconf_option *options;
167 			unsigned int nopts = 0;
168 			size_t k;
169 
170 			for (k = 0; k < reqs[n].nassigns; k++)
171 				if (reqs[n].assigns[k].fileidx == i &&
172 				    strcmp(work[k],
173 				    reqs[n].assigns[k].piece) != 0)
174 					nopts++;
175 			if (nopts == 0)
176 				continue;
177 			options = calloc(nopts + 1, sizeof(*options));
178 			if (options == NULL)
179 				err(EXIT_FAILURE, NULL);
180 			nopts = 0;
181 			for (k = 0; k < reqs[n].nassigns; k++) {
182 				struct bsdconf_option *opt;
183 
184 				if (reqs[n].assigns[k].fileidx != i ||
185 				    strcmp(work[k],
186 				    reqs[n].assigns[k].piece) == 0)
187 					continue;
188 				opt = &options[nopts++];
189 				memset(opt, 0, sizeof(*opt));
190 				opt->type = BSDCONF_TYPE_STR;
191 				opt->directive = reqs[n].name;
192 				opt->match_line = reqs[n].assigns[k].line;
193 				opt->op = reqs[n].assigns[k].op;
194 				if (work[k][0] == '\0') {
195 					opt->action = BSDCONF_ACTION_REMOVE;
196 					opt->value.str = NULL;
197 				} else {
198 					opt->action = BSDCONF_ACTION_SET_VALUE;
199 					opt->value.str = work[k];
200 				}
201 			}
202 			{
203 				int fd;
204 
205 				if ((fd = open(conf_files[i],
206 				    O_WRONLY | O_CREAT | O_EXCL, 0666)) >= 0)
207 					close(fd);
208 				else if (errno != EEXIST)
209 					err(EXIT_FAILURE, "%s", conf_files[i]);
210 			}
211 			if (bsdconf_put(options, conf_files[i],
212 			    bsdconf_format_processing(format),
213 			    bsdconf_format_put(format)) != 0)
214 				err(EXIT_FAILURE, "%s", conf_files[i]);
215 			if (!quiet && verbose_trail) {
216 				for (k = 0; k < nopts; k++) {
217 					struct bsdconf_option *opt;
218 					const char *oval;
219 					const char *nval;
220 
221 					opt = &options[k];
222 					/* Recover old piece from assigns */
223 					oval = "";
224 					nval = opt->value.str != NULL ?
225 					    opt->value.str : "";
226 					for (j = 0; j < reqs[n].nassigns;
227 					    j++) {
228 						if (reqs[n].assigns[j].fileidx
229 						    != i ||
230 						    reqs[n].assigns[j].line !=
231 						    opt->match_line)
232 							continue;
233 						oval =
234 						    reqs[n].assigns[j].piece;
235 						break;
236 					}
237 					if (opt->action ==
238 					    BSDCONF_ACTION_REMOVE)
239 						printf("%s:%u: %s%s%s "
240 						    "(removed)\n",
241 						    conf_files[i],
242 						    opt->match_line,
243 						    reqs[n].name,
244 						    op_str(opt->op), oval);
245 					else
246 						print_write_echo(conf_files[i],
247 						    opt->match_line,
248 						    reqs[n].name, opt->op,
249 						    oval, nval, 1, 0, 0);
250 				}
251 			}
252 			free(options);
253 		}
254 
255 		if (!quiet && !verbose_trail)
256 			print_write_echo(
257 			    reqs[n].srcidx >= 0 ?
258 			    conf_files[reqs[n].srcidx] : NULL,
259 			    0, reqs[n].name, BSDCONF_OP_ASSIGN, old_eff,
260 			    new_eff, 1, 0, 0);
261 		free(reqs[n].value);
262 		reqs[n].value = new_eff;
263 
264 neutralize:
265 		for (i = 0; i < reqs[n].nassigns; i++)
266 			free(work[i]);
267 		free(work);
268 		reqs[n].newvalue = NULL;
269 		reqs[n].listop = '\0';
270 		/* Keep name for any trailing read of the same argv slot */
271 	}
272 
273 	return (rv);
274 }
275