xref: /freebsd/usr.sbin/sysconf/sysconf_edit_assign.c (revision 3fe5961a0b708da599d42cbb6b5e4f030c28ea45)
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