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