xref: /openbsd/usr.bin/make/parsevar.c (revision c9fc29cf)
1*c9fc29cfSespie /*	$OpenBSD: parsevar.c,v 1.17 2023/09/04 11:35:11 espie Exp $	*/
2f7923656Sespie /*	$NetBSD: parse.c,v 1.29 1997/03/10 21:20:04 christos Exp $	*/
3f7923656Sespie 
4f7923656Sespie /*
5f7923656Sespie  * Copyright (c) 2001 Marc Espie.
6f7923656Sespie  *
7f7923656Sespie  * Redistribution and use in source and binary forms, with or without
8f7923656Sespie  * modification, are permitted provided that the following conditions
9f7923656Sespie  * are met:
10f7923656Sespie  * 1. Redistributions of source code must retain the above copyright
11f7923656Sespie  *    notice, this list of conditions and the following disclaimer.
12f7923656Sespie  * 2. Redistributions in binary form must reproduce the above copyright
13f7923656Sespie  *    notice, this list of conditions and the following disclaimer in the
14f7923656Sespie  *    documentation and/or other materials provided with the distribution.
15f7923656Sespie  *
16f7923656Sespie  * THIS SOFTWARE IS PROVIDED BY THE OPENBSD PROJECT AND CONTRIBUTORS
17f7923656Sespie  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18f7923656Sespie  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19f7923656Sespie  * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE OPENBSD
20f7923656Sespie  * PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21f7923656Sespie  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22f7923656Sespie  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23f7923656Sespie  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24f7923656Sespie  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25f7923656Sespie  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26f7923656Sespie  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27f7923656Sespie  */
28f7923656Sespie 
29f7923656Sespie #include <ctype.h>
30f7923656Sespie #include <stddef.h>
31f7923656Sespie #include <stdlib.h>
32f7923656Sespie #include <string.h>
33f7923656Sespie #include "defines.h"
34f7923656Sespie #include "var.h"
35f7923656Sespie #include "varname.h"
36f7923656Sespie #include "error.h"
37f7923656Sespie #include "cmd_exec.h"
38f7923656Sespie #include "parsevar.h"
39f7923656Sespie 
40f7923656Sespie static const char *find_op1(const char *);
41f7923656Sespie static const char *find_op2(const char *);
42d88ab755Sespie static bool parse_variable_assignment(const char *, int);
43f7923656Sespie 
44f7923656Sespie static const char *
45ad2b054bSespie find_op1(const char *p)
46f7923656Sespie {
47f7923656Sespie 	for(;; p++) {
4856afcde6Sespie 		if (ISSPACE(*p) || *p == '$' || *p == '\0')
49f7923656Sespie 			break;
50f7923656Sespie 		if (p[strspn(p, "?:!+")] == '=')
51f7923656Sespie 			break;
52f7923656Sespie 		if (p[0] == ':' && p[1] == 's' && p[2] == 'h')
53f7923656Sespie 			break;
54f7923656Sespie 	}
55f7923656Sespie 	return p;
56f7923656Sespie }
57f7923656Sespie 
58f7923656Sespie static const char *
59ad2b054bSespie find_op2(const char *p)
60f7923656Sespie {
61f7923656Sespie 	for(;; p++) {
6256afcde6Sespie 		if (ISSPACE(*p) || *p == '$' || *p == '\0')
63f7923656Sespie 			break;
64f7923656Sespie 		if (p[strspn(p, "?:!+")] == '=')
65f7923656Sespie 			break;
66f7923656Sespie 	}
67f7923656Sespie 	return p;
68f7923656Sespie }
69f7923656Sespie 
70d88ab755Sespie static bool
71142b1e92Sespie parse_variable_assignment(const char *line, int ctxt)
72f7923656Sespie {
73f7923656Sespie 	const char *arg;
74f7923656Sespie 	char *res1 = NULL, *res2 = NULL;
75c6b7973eSespie #define VAR_INVALID	-1
76f7923656Sespie #define VAR_NORMAL	0
77f7923656Sespie #define VAR_SUBST	1
78f7923656Sespie #define VAR_APPEND	2
79f7923656Sespie #define VAR_SHELL	4
80f7923656Sespie #define VAR_OPT		8
8183f3589bSespie #define VAR_LAZYSHELL	16
8283f3589bSespie #define VAR_SUNSHELL	32
83c6b7973eSespie 	int type;
84f7923656Sespie 	struct Name name;
85f7923656Sespie 
86*c9fc29cfSespie 	arg = VarName_Get(line, &name, NULL, true, find_op1);
87f7923656Sespie 
8856afcde6Sespie 	while (ISSPACE(*arg))
89f7923656Sespie 		arg++;
90f7923656Sespie 
91f7923656Sespie 	type = VAR_NORMAL;
92f7923656Sespie 
9383f3589bSespie 	/* double operators (except for :) are forbidden */
9483f3589bSespie 	/* OPT and APPEND don't match */
9583f3589bSespie 	/* APPEND and LAZYSHELL can't really work */
96c6b7973eSespie 	while (*arg != '=') {
97f7923656Sespie 		/* Check operator type.  */
98f7923656Sespie 		switch (*arg++) {
99f7923656Sespie 		case '+':
10083f3589bSespie 			if (type & (VAR_OPT|VAR_LAZYSHELL|VAR_APPEND))
101c6b7973eSespie 				type = VAR_INVALID;
102c6b7973eSespie 			else
103f7923656Sespie 				type |= VAR_APPEND;
104f7923656Sespie 			break;
105f7923656Sespie 
106f7923656Sespie 		case '?':
107c6b7973eSespie 			if (type & (VAR_OPT|VAR_APPEND))
108c6b7973eSespie 				type = VAR_INVALID;
109c6b7973eSespie 			else
110f7923656Sespie 				type |= VAR_OPT;
111f7923656Sespie 			break;
112f7923656Sespie 
113f7923656Sespie 		case ':':
114*c9fc29cfSespie 			if (strncmp(arg, "sh", 2) == 0) {
11583f3589bSespie 				type = VAR_SUNSHELL;
116f7923656Sespie 				arg += 2;
117f7923656Sespie 				while (*arg != '=' && *arg != '\0')
118f7923656Sespie 					arg++;
119f7923656Sespie 			} else {
120c6b7973eSespie 				if (type & VAR_SUBST)
121c6b7973eSespie 					type = VAR_INVALID;
122c6b7973eSespie 				else
123f7923656Sespie 					type |= VAR_SUBST;
124f7923656Sespie 			}
125f7923656Sespie 			break;
126f7923656Sespie 
127f7923656Sespie 		case '!':
12883f3589bSespie 			if (type & VAR_SHELL) {
12983f3589bSespie 				if (type & (VAR_APPEND))
13083f3589bSespie 					type = VAR_INVALID;
13183f3589bSespie 				else
13283f3589bSespie 					type = VAR_LAZYSHELL;
13383f3589bSespie 			} else if (type & (VAR_LAZYSHELL|VAR_SUNSHELL))
134c6b7973eSespie 				type = VAR_INVALID;
135c6b7973eSespie 			else
136f7923656Sespie 				type |= VAR_SHELL;
137f7923656Sespie 			break;
138f7923656Sespie 
139f7923656Sespie 		default:
140c6b7973eSespie 			type = VAR_INVALID;
141c6b7973eSespie 			break;
142c6b7973eSespie 		}
143c6b7973eSespie 		if (type == VAR_INVALID) {
144f7923656Sespie 			VarName_Free(&name);
145f7923656Sespie 			return false;
146f7923656Sespie 		}
147f7923656Sespie 	}
148f7923656Sespie 
149c6b7973eSespie 	arg++;
15056afcde6Sespie 	while (ISSPACE(*arg))
151f7923656Sespie 		arg++;
152f7923656Sespie 	/* If the variable already has a value, we don't do anything.  */
153d88ab755Sespie 	if ((type & VAR_OPT) && Var_Definedi(name.s, name.e)) {
154f7923656Sespie 		VarName_Free(&name);
155f7923656Sespie 		return true;
156f7923656Sespie 	}
15783f3589bSespie 	if (type & (VAR_SHELL|VAR_SUNSHELL)) {
158f7923656Sespie 		char *err;
159f7923656Sespie 
160f7923656Sespie 		if (strchr(arg, '$') != NULL) {
161f7923656Sespie 			char *sub;
162142b1e92Sespie 			/* There's a dollar sign in the command, so perform
163142b1e92Sespie 			 * variable expansion on the whole thing. */
164f7923656Sespie 			sub = Var_Subst(arg, NULL, true);
165f7923656Sespie 			res1 = Cmd_Exec(sub, &err);
166f7923656Sespie 			free(sub);
167f7923656Sespie 		} else
168f7923656Sespie 			res1 = Cmd_Exec(arg, &err);
169f7923656Sespie 
170f7923656Sespie 		if (err)
171f7923656Sespie 			Parse_Error(PARSE_WARNING, err, arg);
172f7923656Sespie 		arg = res1;
173f7923656Sespie 	}
17483f3589bSespie 	if (type & VAR_LAZYSHELL) {
17583f3589bSespie 		if (strchr(arg, '$') != NULL) {
17683f3589bSespie 			/* There's a dollar sign in the command, so perform
17783f3589bSespie 			 * variable expansion on the whole thing. */
17883f3589bSespie 			arg = Var_Subst(arg, NULL, true);
17983f3589bSespie 		}
18083f3589bSespie 	}
181f7923656Sespie 	if (type & VAR_SUBST) {
182f7923656Sespie 		/*
183142b1e92Sespie 		 * Allow variables in the old value to be undefined, but leave
184142b1e92Sespie 		 * their invocation alone -- this is done by forcing
185142b1e92Sespie 		 * errorIsOkay to be false.
186142b1e92Sespie 		 * XXX: This can cause recursive variables, but that's not
187142b1e92Sespie 		 * hard to do, and this allows someone to do something like
188f7923656Sespie 		 *
189f7923656Sespie 		 *  CFLAGS = $(.INCLUDES)
190f7923656Sespie 		 *  CFLAGS := -I.. $(CFLAGS)
191f7923656Sespie 		 *
192f7923656Sespie 		 * And not get an error.
193f7923656Sespie 		 */
19493a10c24Sespie 		bool   saved = errorIsOkay;
195f7923656Sespie 
19693a10c24Sespie 		errorIsOkay = false;
197f7923656Sespie 		/* ensure the variable is set to something to avoid `variable
198f7923656Sespie 		 * is recursive' errors.  */
199c6b7973eSespie 		if (!Var_Definedi(name.s, name.e))
2005a5ca93eSespie 			Var_Seti_with_ctxt(name.s, name.e, "", ctxt);
201f7923656Sespie 
202d88ab755Sespie 		res2 = Var_Subst(arg, NULL, false);
20393a10c24Sespie 		errorIsOkay = saved;
204f7923656Sespie 
205f7923656Sespie 		arg = res2;
206f7923656Sespie 	}
207f7923656Sespie 
208f7923656Sespie 	if (type & VAR_APPEND)
2095a5ca93eSespie 		Var_Appendi_with_ctxt(name.s, name.e, arg, ctxt);
210f7923656Sespie 	else
2115a5ca93eSespie 		Var_Seti_with_ctxt(name.s, name.e, arg, ctxt);
21283f3589bSespie 	if (type & VAR_LAZYSHELL)
21383f3589bSespie 		Var_Mark(name.s, name.e, VAR_EXEC_LATER);
214f7923656Sespie 
215f7923656Sespie 	VarName_Free(&name);
216f7923656Sespie 	free(res2);
217f7923656Sespie 	free(res1);
218f7923656Sespie 	return true;
219f7923656Sespie }
220f7923656Sespie 
221d88ab755Sespie bool
22216620742Sespie Parse_As_Var_Assignment(const char *line)
223d88ab755Sespie {
224d88ab755Sespie 	return parse_variable_assignment(line, VAR_GLOBAL);
225d88ab755Sespie }
226d88ab755Sespie 
227d88ab755Sespie bool
228d88ab755Sespie Parse_CmdlineVar(const char *line)
229d88ab755Sespie {
230d88ab755Sespie 	bool result;
23193a10c24Sespie 	bool saved = errorIsOkay;
232d88ab755Sespie 
23393a10c24Sespie 	errorIsOkay = false;
234d88ab755Sespie 	result = parse_variable_assignment(line, VAR_CMD);
23594c8ed6aSespie 	errorIsOkay = saved;
236d88ab755Sespie 	return result;
237d88ab755Sespie }
238d88ab755Sespie 
239