1 /** 2 * Parses compiler settings from a .ini file. 3 * 4 * Copyright: Copyright (C) 1994-1998 by Symantec 5 * Copyright (C) 2000-2023 by The D Language Foundation, All Rights Reserved 6 * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) 7 * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 8 * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/dinifile.d, _dinifile.d) 9 * Documentation: https://dlang.org/phobos/dmd_dinifile.html 10 * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/dinifile.d 11 */ 12 13 module dmd.dinifile; 14 15 import core.stdc.ctype; 16 import core.stdc.string; 17 import core.sys.posix.stdlib; 18 import core.sys.windows.winbase; 19 import core.sys.windows.windef; 20 21 import dmd.errors; 22 import dmd.globals; 23 import dmd.location; 24 import dmd.root.env; 25 import dmd.root.rmem; 26 import dmd.root.filename; 27 import dmd.common.outbuffer; 28 import dmd.root.port; 29 import dmd.root.string; 30 import dmd.root.stringtable; 31 32 private enum LOG = false; 33 34 /***************************** 35 * Find the config file 36 * Params: 37 * argv0 = program name (argv[0]) 38 * inifile = .ini file name 39 * Returns: 40 * file path of the config file or NULL 41 * Note: this is a memory leak 42 */ 43 const(char)[] findConfFile(const(char)[] argv0, const(char)[] inifile) 44 { 45 static if (LOG) 46 { 47 printf("findinifile(argv0 = '%.*s', inifile = '%.*s')\n", 48 cast(int)argv0.length, argv0.ptr, cast(int)inifile.length, inifile.ptr); 49 } 50 if (FileName.absolute(inifile)) 51 return inifile; 52 if (FileName.exists(inifile)) 53 return inifile; 54 /* Look for inifile in the following sequence of places: 55 * o current directory 56 * o home directory 57 * o exe directory (windows) 58 * o directory off of argv0 59 * o SYSCONFDIR=/etc (non-windows) 60 */ 61 auto filename = FileName.combine(getenv("HOME").toDString, inifile); 62 if (FileName.exists(filename)) 63 return filename; 64 version (Windows) 65 { 66 // This fix by Tim Matthews 67 char[MAX_PATH + 1] resolved_name; 68 const len = GetModuleFileNameA(null, resolved_name.ptr, MAX_PATH + 1); 69 if (len && FileName.exists(resolved_name[0 .. len])) 70 { 71 filename = FileName.replaceName(resolved_name[0 .. len], inifile); 72 if (FileName.exists(filename)) 73 return filename; 74 } 75 } 76 filename = FileName.replaceName(argv0, inifile); 77 if (FileName.exists(filename)) 78 return filename; 79 version (Posix) 80 { 81 // Search PATH for argv0 82 const p = getenv("PATH"); 83 static if (LOG) 84 { 85 printf("\tPATH='%s'\n", p); 86 } 87 auto abspath = FileName.searchPath(p, argv0, false); 88 if (abspath) 89 { 90 auto absname = FileName.replaceName(abspath, inifile); 91 if (FileName.exists(absname)) 92 return absname; 93 } 94 // Resolve symbolic links 95 filename = FileName.canonicalName(abspath ? abspath : argv0); 96 if (filename) 97 { 98 filename = FileName.replaceName(filename, inifile); 99 if (FileName.exists(filename)) 100 return filename; 101 } 102 // Search SYSCONFDIR=/etc for inifile 103 filename = FileName.combine(import("SYSCONFDIR.imp"), inifile); 104 } 105 return filename; 106 } 107 108 /********************************** 109 * Read from environment, looking for cached value first. 110 * Params: 111 * environment = cached copy of the environment 112 * name = name to look for 113 * Returns: 114 * environment value corresponding to name 115 */ 116 const(char)* readFromEnv(const ref StringTable!(char*) environment, const(char)* name) 117 { 118 const len = strlen(name); 119 const sv = environment.lookup(name, len); 120 if (sv && sv.value) 121 return sv.value; // get cached value 122 return getenv(name); 123 } 124 125 /********************************* 126 * Write to our copy of the environment, not the real environment 127 */ 128 private bool writeToEnv(ref StringTable!(char*) environment, char* nameEqValue) 129 { 130 auto p = strchr(nameEqValue, '='); 131 if (!p) 132 return false; 133 auto sv = environment.update(nameEqValue, p - nameEqValue); 134 sv.value = p + 1; 135 return true; 136 } 137 138 /************************************ 139 * Update real environment with our copy. 140 * Params: 141 * environment = our copy of the environment 142 */ 143 void updateRealEnvironment(ref StringTable!(char*) environment) 144 { 145 foreach (sv; environment) 146 { 147 const name = sv.toDchars(); 148 const value = sv.value; 149 if (!value) // deleted? 150 continue; 151 if (putenvRestorable(name.toDString, value.toDString)) 152 assert(0); 153 } 154 } 155 156 /***************************** 157 * Read and analyze .ini file. 158 * Write the entries into environment as 159 * well as any entries in one of the specified section(s). 160 * 161 * Params: 162 * environment = our own cache of the program environment 163 * filename = name of the file being parsed 164 * path = what @P will expand to 165 * buffer = contents of configuration file 166 * sections = section names 167 */ 168 void parseConfFile(ref StringTable!(char*) environment, const(char)[] filename, const(char)[] path, const(ubyte)[] buffer, const(Strings)* sections) 169 { 170 /******************** 171 * Skip spaces. 172 */ 173 static inout(char)* skipspace(inout(char)* p) 174 { 175 while (isspace(*p)) 176 p++; 177 return p; 178 } 179 180 // Parse into lines 181 bool envsection = true; // default is to read 182 OutBuffer buf; 183 bool eof = false; 184 int lineNum = 0; 185 for (size_t i = 0; i < buffer.length && !eof; i++) 186 { 187 Lstart: 188 const linestart = i; 189 for (; i < buffer.length; i++) 190 { 191 switch (buffer[i]) 192 { 193 case '\r': 194 break; 195 case '\n': 196 // Skip if it was preceded by '\r' 197 if (i && buffer[i - 1] == '\r') 198 { 199 i++; 200 goto Lstart; 201 } 202 break; 203 case 0: 204 case 0x1A: 205 eof = true; 206 break; 207 default: 208 continue; 209 } 210 break; 211 } 212 ++lineNum; 213 buf.setsize(0); 214 // First, expand the macros. 215 // Macros are bracketed by % characters. 216 Kloop: 217 for (size_t k = 0; k < i - linestart; ++k) 218 { 219 // The line is buffer[linestart..i] 220 const line = cast(const char*)&buffer[linestart]; 221 if (line[k] == '%') 222 { 223 foreach (size_t j; k + 1 .. i - linestart) 224 { 225 if (line[j] != '%') 226 continue; 227 if (j - k == 3 && Port.memicmp(&line[k + 1], "@P", 2) == 0) 228 { 229 // %@P% is special meaning the path to the .ini file 230 auto p = path; 231 if (!p.length) 232 p = "."; 233 buf.writestring(p); 234 } 235 else 236 { 237 auto len2 = j - k; 238 auto p = cast(char*)Mem.check(malloc(len2)); 239 len2--; 240 memcpy(p, &line[k + 1], len2); 241 p[len2] = 0; 242 Port.strupr(p); 243 const penv = readFromEnv(environment, p); 244 if (penv) 245 buf.writestring(penv); 246 free(p); 247 } 248 k = j; 249 continue Kloop; 250 } 251 } 252 buf.writeByte(line[k]); 253 } 254 255 // Remove trailing spaces 256 const slice = buf[]; 257 auto slicelen = slice.length; 258 while (slicelen && isspace(slice[slicelen - 1])) 259 --slicelen; 260 buf.setsize(slicelen); 261 262 auto p = buf.peekChars(); 263 // The expanded line is in p. 264 // Now parse it for meaning. 265 p = skipspace(p); 266 switch (*p) 267 { 268 case ';': 269 // comment 270 case 0: 271 // blank 272 break; 273 case '[': 274 // look for [Environment] 275 p = skipspace(p + 1); 276 char* pn; 277 for (pn = p; isalnum(*pn); pn++) 278 { 279 } 280 if (*skipspace(pn) != ']') 281 { 282 // malformed [sectionname], so just say we're not in a section 283 envsection = false; 284 break; 285 } 286 /* Search sectionnamev[] for p..pn and set envsection to true if it's there 287 */ 288 for (size_t j = 0; 1; ++j) 289 { 290 if (j == sections.length) 291 { 292 // Didn't find it 293 envsection = false; 294 break; 295 } 296 const sectionname = (*sections)[j]; 297 const len = strlen(sectionname); 298 if (pn - p == len && Port.memicmp(p, sectionname, len) == 0) 299 { 300 envsection = true; 301 break; 302 } 303 } 304 break; 305 default: 306 if (envsection) 307 { 308 auto pn = p; 309 // Convert name to upper case; 310 // remove spaces bracketing = 311 for (; *p; p++) 312 { 313 if (islower(*p)) 314 *p &= ~0x20; 315 else if (isspace(*p)) 316 { 317 memmove(p, p + 1, strlen(p)); 318 p--; 319 } 320 else if (p[0] == '?' && p[1] == '=') 321 { 322 *p = '\0'; 323 if (readFromEnv(environment, pn)) 324 { 325 pn = null; 326 break; 327 } 328 // remove the '?' and resume parsing starting from 329 // '=' again so the regular variable format is 330 // parsed 331 memmove(p, p + 1, strlen(p + 1) + 1); 332 p--; 333 } 334 else if (*p == '=') 335 { 336 p++; 337 while (isspace(*p)) 338 memmove(p, p + 1, strlen(p)); 339 break; 340 } 341 } 342 if (pn) 343 { 344 auto pns = cast(char*)Mem.check(strdup(pn)); 345 if (!writeToEnv(environment, pns)) 346 { 347 const loc = Loc(filename.xarraydup.ptr, lineNum, 0); // TODO: use r-value when `error` supports it 348 error(loc, "use `NAME=value` syntax, not `%s`", pn); 349 fatal(); 350 } 351 static if (LOG) 352 { 353 printf("\tputenv('%s')\n", pn); 354 //printf("getenv(\"TEST\") = '%s'\n",getenv("TEST")); 355 } 356 } 357 } 358 break; 359 } 360 } 361 }