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