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 }