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