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 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 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 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