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 * 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 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 209 Parse_As_Var_Assignment(const char *line) 210 { 211 return parse_variable_assignment(line, VAR_GLOBAL); 212 } 213 214 bool 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