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 * CLI assignment parsing and sysrc(8)-style list word edits. 10 */ 11 12 #include <ctype.h> 13 14 #include "sysconf_priv.h" 15 16 /* 17 * Split a `name[=value]' argument into a request. With `remove' set, the 18 * argument is a bare name to delete. For make(1)-style targets, a trailing 19 * operator character on the name (`+', `?', `:', `!') selects the 20 * corresponding assignment modifier: `+=' appends a new `name+=value' line 21 * (make accumulates; we never rewrite a prior assignment), while `=', `?=', 22 * `:=', and `!=' rewrite or add a single assignment as today. `name-=word' 23 * is a list strike: the last assignment whose value contains each word is 24 * rewritten (or removed if emptied); see do_make_strikes(). For every other 25 * target, `name+=word' and `name-=word' are sysrc(8)-style list edits 26 * against the effective value (see merge_list_requests() below). 27 */ 28 void 29 split_request(char *arg, struct request *req, int remove) 30 { 31 char *eq; 32 size_t n; 33 34 req->name = arg; 35 req->newvalue = NULL; 36 req->op = BSDCONF_OP_DEFAULT; 37 req->listop = '\0'; 38 req->value = NULL; 39 req->found = 0; 40 req->append_present = 0; 41 req->remove = remove; 42 req->srcidx = -1; 43 req->line = 0; 44 req->trail = NULL; 45 req->ntrail = 0; 46 req->assigns = NULL; 47 req->nassigns = 0; 48 if ((req->infile = calloc(nconf_files, 1)) == NULL) 49 err(EXIT_FAILURE, NULL); 50 51 if (remove) { 52 if (strchr(arg, '=') != NULL) 53 errx(EXIT_FAILURE, 54 "%s: -x does not take a value", arg); 55 return; 56 } 57 58 if ((eq = strchr(arg, '=')) == NULL) 59 return; 60 61 *eq = '\0'; 62 63 /* 64 * Strip one balanced layer of double-quotes from the value as a 65 * courtesy (e.g., a literal zfs_load="YES" from a quoted shell 66 * word); the library re-applies the target format's quoting rules 67 * on output. 68 */ 69 req->newvalue = bsdconf_unquote(eq + 1); 70 71 /* Split an operator character off the tail of the name */ 72 n = strlen(arg); 73 if ((bsdconf_format_processing(format) & 74 BSDCONF_OPERATOR_EQUALS) != 0 && n > 1) { 75 switch (arg[n - 1]) { 76 case '+': req->op = BSDCONF_OP_APPEND; break; 77 case '?': req->op = BSDCONF_OP_COND; break; 78 case ':': req->op = BSDCONF_OP_EXPAND; break; 79 case '!': req->op = BSDCONF_OP_SHELL; break; 80 case '-': 81 /* make(1) has no `-='; treat as a list strike */ 82 req->listop = '-'; 83 arg[n - 1] = '\0'; 84 break; 85 } 86 if (req->op != BSDCONF_OP_DEFAULT) 87 arg[n - 1] = '\0'; 88 } else if (n > 1 && (arg[n - 1] == '+' || arg[n - 1] == '-')) { 89 req->listop = arg[n - 1]; 90 arg[n - 1] = '\0'; 91 } 92 93 if (*arg == '\0') 94 errx(EXIT_FAILURE, "empty directive name"); 95 } 96 97 /* True when `req' is a make/src `name-=word' strike (not a sysrc list edit). */ 98 /* 99 * Test whether the whitespace-separated `list' contains `word' (of length 100 * `wlen') as a whole word. 101 */ 102 int 103 list_has(const char *list, const char *word, size_t wlen) 104 { 105 size_t len; 106 107 if (list == NULL) 108 return (0); 109 while (*list != '\0') { 110 list += strspn(list, " \t"); 111 if ((len = strcspn(list, " \t")) == 0) 112 break; 113 if (len == wlen && strncmp(list, word, wlen) == 0) 114 return (1); 115 list += len; 116 } 117 118 return (0); 119 } 120 121 /* 122 * Merge the whitespace-separated `words' into (op `+') or out of (op `-') 123 * the whitespace-separated list `current' (NULL for an unset directive), 124 * appending only words not already present and joining the survivors with 125 * single spaces. Returns newly allocated storage. 126 */ 127 char * 128 list_merge(const char *current, const char *words, int op) 129 { 130 size_t len; 131 size_t rlen = 0; 132 size_t size; 133 char *result; 134 const char *token; 135 136 if (current == NULL) 137 current = ""; 138 size = strlen(current) + strlen(words) + 2; 139 if ((result = malloc(size)) == NULL) 140 err(EXIT_FAILURE, NULL); 141 result[0] = '\0'; 142 143 /* Copy the current words, striking matches under `-' */ 144 token = current; 145 while (*token != '\0') { 146 token += strspn(token, " \t"); 147 if ((len = strcspn(token, " \t")) == 0) 148 break; 149 if (op == '-' && list_has(words, token, len)) { 150 token += len; 151 continue; 152 } 153 if (rlen > 0) 154 result[rlen++] = ' '; 155 memcpy(result + rlen, token, len); 156 rlen += len; 157 result[rlen] = '\0'; 158 token += len; 159 } 160 161 /* Append the new words not already present under `+' */ 162 if (op == '+') { 163 token = words; 164 while (*token != '\0') { 165 token += strspn(token, " \t"); 166 if ((len = strcspn(token, " \t")) == 0) 167 break; 168 if (!list_has(result, token, len)) { 169 if (rlen > 0) 170 result[rlen++] = ' '; 171 memcpy(result + rlen, token, len); 172 rlen += len; 173 result[rlen] = '\0'; 174 } 175 token += len; 176 } 177 } 178 179 return (result); 180 } 181 182 /* 183 * Materialize sysrc(8)-style list-edit requests (`name+=word ...' and 184 * `name-=word ...') against the effective values recorded by scan_pass(), 185 * rewriting each request's pending value in place so the ordinary write, 186 * check, and echo machinery sees a plain assignment. Make/src `name-=word' 187 * strikes are handled separately by do_make_strikes(). Appending to an 188 * unset directive creates it; striking words from one is reported like any 189 * other unknown directive (there is nothing to remove) and the request is 190 * neutralized. Returns EXIT_SUCCESS or EXIT_FAILURE. 191 */ 192 int 193 merge_list_requests(void) 194 { 195 int rv = EXIT_SUCCESS; 196 unsigned int n; 197 198 for (n = 0; n < nreqs; n++) { 199 if (reqs[n].listop == '\0' || reqs[n].newvalue == NULL) 200 continue; 201 /* make/src `-=` is a per-statement strike, not a list merge */ 202 if (make_strike_p(&reqs[n])) 203 continue; 204 if (reqs[n].listop == '-' && !reqs[n].found) { 205 if (!ignore_unknown) { 206 if (!quiet) 207 warnx("unknown directive '%s'", 208 reqs[n].name); 209 rv = EXIT_FAILURE; 210 } 211 /* Convert to a no-op */ 212 reqs[n].newvalue = NULL; 213 reqs[n].name = ""; 214 continue; 215 } 216 reqs[n].newvalue = list_merge(reqs[n].value, 217 reqs[n].newvalue, reqs[n].listop); 218 } 219 220 return (rv); 221 } 222