xref: /openbsd/usr.bin/make/parsevar.c (revision fc61954a)
1 /*	$OpenBSD: parsevar.c,v 1.16 2016/10/23 14:54:14 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 "config.h"
34 #include "defines.h"
35 #include "var.h"
36 #include "varname.h"
37 #include "error.h"
38 #include "cmd_exec.h"
39 #include "parsevar.h"
40 
41 static const char *find_op1(const char *);
42 static const char *find_op2(const char *);
43 static bool parse_variable_assignment(const char *, int);
44 
45 static const char *
46 find_op1(const char *p)
47 {
48 	for(;; p++) {
49 		if (ISSPACE(*p) || *p == '$' || *p == '\0')
50 			break;
51 		if (p[strspn(p, "?:!+")] == '=')
52 			break;
53 		if (p[0] == ':' && p[1] == 's' && p[2] == 'h')
54 			break;
55 	}
56 	return p;
57 }
58 
59 static const char *
60 find_op2(const char *p)
61 {
62 	for(;; p++) {
63 		if (ISSPACE(*p) || *p == '$' || *p == '\0')
64 			break;
65 		if (p[strspn(p, "?:!+")] == '=')
66 			break;
67 	}
68 	return p;
69 }
70 
71 static bool
72 parse_variable_assignment(const char *line, int ctxt)
73 {
74 	const char *arg;
75 	char *res1 = NULL, *res2 = NULL;
76 #define VAR_INVALID	-1
77 #define VAR_NORMAL	0
78 #define VAR_SUBST	1
79 #define VAR_APPEND	2
80 #define VAR_SHELL	4
81 #define VAR_OPT		8
82 #define VAR_LAZYSHELL	16
83 #define VAR_SUNSHELL	32
84 	int type;
85 	struct Name name;
86 
87 	arg = VarName_Get(line, &name, NULL, true,
88 	    FEATURES(FEATURE_SUNSHCMD) ? find_op1 : find_op2);
89 
90 	while (ISSPACE(*arg))
91 		arg++;
92 
93 	type = VAR_NORMAL;
94 
95 	/* double operators (except for :) are forbidden */
96 	/* OPT and APPEND don't match */
97 	/* APPEND and LAZYSHELL can't really work */
98 	while (*arg != '=') {
99 		/* Check operator type.  */
100 		switch (*arg++) {
101 		case '+':
102 			if (type & (VAR_OPT|VAR_LAZYSHELL|VAR_APPEND))
103 				type = VAR_INVALID;
104 			else
105 				type |= VAR_APPEND;
106 			break;
107 
108 		case '?':
109 			if (type & (VAR_OPT|VAR_APPEND))
110 				type = VAR_INVALID;
111 			else
112 				type |= VAR_OPT;
113 			break;
114 
115 		case ':':
116 			if (FEATURES(FEATURE_SUNSHCMD) &&
117 			    strncmp(arg, "sh", 2) == 0) {
118 				type = VAR_SUNSHELL;
119 				arg += 2;
120 				while (*arg != '=' && *arg != '\0')
121 					arg++;
122 			} else {
123 				if (type & VAR_SUBST)
124 					type = VAR_INVALID;
125 				else
126 					type |= VAR_SUBST;
127 			}
128 			break;
129 
130 		case '!':
131 			if (type & VAR_SHELL) {
132 				if (type & (VAR_APPEND))
133 					type = VAR_INVALID;
134 				else
135 					type = VAR_LAZYSHELL;
136 			} else if (type & (VAR_LAZYSHELL|VAR_SUNSHELL))
137 				type = VAR_INVALID;
138 			else
139 				type |= VAR_SHELL;
140 			break;
141 
142 		default:
143 			type = VAR_INVALID;
144 			break;
145 		}
146 		if (type == VAR_INVALID) {
147 			VarName_Free(&name);
148 			return false;
149 		}
150 	}
151 
152 	arg++;
153 	while (ISSPACE(*arg))
154 		arg++;
155 	/* If the variable already has a value, we don't do anything.  */
156 	if ((type & VAR_OPT) && Var_Definedi(name.s, name.e)) {
157 		VarName_Free(&name);
158 		return true;
159 	}
160 	if (type & (VAR_SHELL|VAR_SUNSHELL)) {
161 		char *err;
162 
163 		if (strchr(arg, '$') != NULL) {
164 			char *sub;
165 			/* There's a dollar sign in the command, so perform
166 			 * variable expansion on the whole thing. */
167 			sub = Var_Subst(arg, NULL, true);
168 			res1 = Cmd_Exec(sub, &err);
169 			free(sub);
170 		} else
171 			res1 = Cmd_Exec(arg, &err);
172 
173 		if (err)
174 			Parse_Error(PARSE_WARNING, err, arg);
175 		arg = res1;
176 	}
177 	if (type & VAR_LAZYSHELL) {
178 		if (strchr(arg, '$') != NULL) {
179 			/* There's a dollar sign in the command, so perform
180 			 * variable expansion on the whole thing. */
181 			arg = Var_Subst(arg, NULL, true);
182 		}
183 	}
184 	if (type & VAR_SUBST) {
185 		/*
186 		 * Allow variables in the old value to be undefined, but leave
187 		 * their invocation alone -- this is done by forcing
188 		 * errorIsOkay to be false.
189 		 * XXX: This can cause recursive variables, but that's not
190 		 * hard to do, and this allows someone to do something like
191 		 *
192 		 *  CFLAGS = $(.INCLUDES)
193 		 *  CFLAGS := -I.. $(CFLAGS)
194 		 *
195 		 * And not get an error.
196 		 */
197 		bool   saved = errorIsOkay;
198 
199 		errorIsOkay = false;
200 		/* ensure the variable is set to something to avoid `variable
201 		 * is recursive' errors.  */
202 		if (!Var_Definedi(name.s, name.e))
203 			Var_Seti_with_ctxt(name.s, name.e, "", ctxt);
204 
205 		res2 = Var_Subst(arg, NULL, false);
206 		errorIsOkay = saved;
207 
208 		arg = res2;
209 	}
210 
211 	if (type & VAR_APPEND)
212 		Var_Appendi_with_ctxt(name.s, name.e, arg, ctxt);
213 	else
214 		Var_Seti_with_ctxt(name.s, name.e, arg, ctxt);
215 	if (type & VAR_LAZYSHELL)
216 		Var_Mark(name.s, name.e, VAR_EXEC_LATER);
217 
218 	VarName_Free(&name);
219 	free(res2);
220 	free(res1);
221 	return true;
222 }
223 
224 bool
225 Parse_As_Var_Assignment(const char *line)
226 {
227 	return parse_variable_assignment(line, VAR_GLOBAL);
228 }
229 
230 bool
231 Parse_CmdlineVar(const char *line)
232 {
233 	bool result;
234 	bool saved = errorIsOkay;
235 
236 	errorIsOkay = false;
237 	result = parse_variable_assignment(line, VAR_CMD);
238 	errorIsOkay = saved;
239 	return result;
240 }
241 
242