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