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