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