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