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