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