1 /*******************************************************************************
2 
3         copyright:      Copyright (c) 2007 Tango. All rights reserved
4 
5         license:        BSD style: $(LICENSE)
6 
7         version:        Feb 2007: Initial release
8 
9         author:         Deewiant, Maxter, Gregor, Kris
10 
11 *******************************************************************************/
12 
13 module tango.sys.Environment;
14 
15 private import  tango.sys.Common;
16 
17 private import  tango.io.Path,
18                 tango.io.FilePath;
19 
20 private import  tango.core.Exception;
21 
22 private import  tango.io.model.IFile;
23 
24 private import  Text = tango.text.Util;
25 
26 private import  tango.core.Octal;
27 
28 /*******************************************************************************
29 
30         Platform decls
31 
32 *******************************************************************************/
33 
34 version (Windows)
35 {
36         private import tango.text.convert.Utf;
37 
38         pragma (lib, "kernel32.lib");
39 
40         extern (Windows)
41         {
42                 private void* GetEnvironmentStringsW();
43                 private bool FreeEnvironmentStringsW(wchar**);
44         }
45         extern (Windows)
46         {
47                 private int SetEnvironmentVariableW(const(wchar)*, const(wchar)*);
48                 private uint GetEnvironmentVariableW(const(wchar)*, wchar*, uint);
49                 private const int ERROR_ENVVAR_NOT_FOUND = 203;
50         }
51 }
52 else
53 {
54     version(OSX)
55     {
56         extern (C) char*** _NSGetEnviron();
57         private __gshared char** environ;
58         
59         shared static this ()
60         {
61             environ = *_NSGetEnviron();
62         }
63     }
64     
65     else
66         private extern (C) extern __gshared char** environ;
67 
68     import tango.stdc.posix.stdlib;
69     import tango.stdc.string;
70     static import tango.stdc.posix.unistd;
71 }
72 
73 
74 /*******************************************************************************
75 
76         Exposes the system Environment settings, along with some handy
77         utilities
78 
79 *******************************************************************************/
80 
81 struct Environment
82 {
83         public alias cwd directory;
84                     
85         /***********************************************************************
86 
87                 Throw an exception
88 
89         ***********************************************************************/
90 
91         private static void exception (immutable(char)[] msg)
92         {
93                 throw new PlatformException (msg);
94         }
95         
96         /***********************************************************************
97 
98             Returns an absolute version of the provided path, where cwd is used
99             as the prefix.
100 
101             The provided path is returned as is if already absolute.
102 
103         ***********************************************************************/
104 
105         static char[] toAbsolute(char[] path)
106         {
107             scope fp = new FilePath(path);
108             if (fp.isAbsolute)
109                 return path;
110 
111             fp.absolute(cwd());
112             return fp.cString()[0..$-1];
113         }
114 
115         /***********************************************************************
116 
117                 Returns the full path location of the provided executable
118                 file, rifling through the PATH as necessary.
119 
120                 Returns null if the provided filename was not found
121 
122         ***********************************************************************/
123 
124         static FilePath exePath (char[] file)
125         {
126                 auto bin = new FilePath (file);
127 
128                 // on Windows, this is a .exe
129                 version (Windows)
130                          if (bin.ext.length is 0)
131                              bin.suffix = "exe";
132 
133                 // is this a directory? Potentially make it absolute
134                 if (bin.isChild && !bin.isAbsolute)
135                     return bin.absolute (cwd());
136 
137                 // is it in cwd?
138                 version (Windows)
139                          if (bin.path(cwd()).exists)
140                              return bin;
141 
142                 // rifle through the path (after converting to standard format)
143                 foreach (pe; Text.patterns (standard(get("PATH")), tango.io.model.IFile.FileConst.SystemPathString))
144                          if (bin.path(pe).exists)
145                          {
146                              version (Windows)
147                                       return bin;
148                                   else
149                                      {
150                                      stat_t stats;
151                                      stat(bin.cString().ptr, &stats);
152                                      if (stats.st_mode & octal!(100))
153                                          return bin;
154                                      }
155                          }
156                 return null;
157         }
158 
159         /***********************************************************************
160 
161                 Windows implementation
162 
163         ***********************************************************************/
164 
165         version (Windows)
166         {
167                 /**************************************************************
168 
169                         Returns the provided 'def' value if the variable 
170                         does not exist
171 
172                 **************************************************************/
173 
174                 static char[] get (const(char)[] variable, char[] def = null)
175                 {
176                         const(wchar)[] var = toString16(variable) ~ "\0";
177 
178                         uint size = GetEnvironmentVariableW(var.ptr, cast(wchar*)null, 0);
179                         if (size is 0)
180                            {
181                            if (SysError.lastCode is ERROR_ENVVAR_NOT_FOUND)
182                                return def;
183                            else
184                               exception (SysError.lastMsg.idup);
185                            }
186 
187                         auto buffer = new wchar[size];
188                         size = GetEnvironmentVariableW(var.ptr, buffer.ptr, size);
189                         if (size is 0)
190                             exception (SysError.lastMsg.idup);
191 
192                         return toString (buffer[0 .. size]);
193                 }
194 
195                 /**************************************************************
196 
197                         clears the variable if value is null or empty
198 
199                 **************************************************************/
200 
201                 static void set (const(char)[] variable, const(char)[] value = null)
202                 {
203                         const(wchar) * var, val;
204 
205                         var = (toString16 (variable) ~ "\0").ptr;
206 
207                         if (value.length > 0)
208                             val = (toString16 (value) ~ "\0").ptr;
209 
210                         if (! SetEnvironmentVariableW(var, val))
211                               exception (SysError.lastMsg.idup);
212                 }
213 
214                 /**************************************************************
215 
216                         Get all set environment variables as an associative
217                         array.
218 
219                 **************************************************************/
220 
221                 static char[][char[]] get ()
222                 {
223                         char[][char[]] arr;
224 
225                         wchar[] key = new wchar[20],
226                                 value = new wchar[40];
227 
228                         wchar** env = cast(wchar**) GetEnvironmentStringsW();
229                         scope (exit)
230                                FreeEnvironmentStringsW (env);
231 
232                         for (wchar* str = cast(wchar*) env; *str; ++str)
233                             {
234                             size_t k = 0, v = 0;
235 
236                             while (*str != '=')
237                                   {
238                                   key[k++] = *str++;
239 
240                                   if (k is key.length)
241                                       key.length = 2 * key.length;
242                                   }
243 
244                             ++str;
245 
246                             while (*str)
247                                   {
248                                   value [v++] = *str++;
249 
250                                   if (v is value.length)
251                                       value.length = 2 * value.length;
252                                   }
253 
254                             arr [toString(key[0 .. k]).idup] = toString(value[0 .. v]);
255                             }
256 
257                         return arr;
258                 }
259 
260                 /**************************************************************
261 
262                         Set the current working directory
263 
264                 **************************************************************/
265 
266                 static void cwd (const(char)[] path)
267                 {
268                         version (Win32SansUnicode)
269                                 {
270                                 char[MAX_PATH+1] tmp = void;
271                                 tmp[0..path.length] = path;
272                                 tmp[path.length] = 0;
273 
274                                 if (! SetCurrentDirectoryA (tmp.ptr))
275                                       exception ("Failed to set current directory");
276                                 }
277                              else
278                                 {
279                                 // convert into output buffer
280                                 wchar[MAX_PATH+1] tmp = void;
281                                 assert (path.length < tmp.length);
282                                 auto i = MultiByteToWideChar (CP_UTF8, 0, 
283                                                               cast(PCHAR)path.ptr, path.length, 
284                                                               tmp.ptr, tmp.length);
285                                 tmp[i] = 0;
286 
287                                 if (! SetCurrentDirectoryW (tmp.ptr))
288                                       exception ("Failed to set current directory");
289                                 }
290                 }
291 
292                 /**************************************************************
293 
294                         Get the current working directory
295 
296                 **************************************************************/
297 
298                 static char[] cwd ()
299                 {
300                         char[] path;
301 
302                         version (Win32SansUnicode)
303                                 {
304                                 int len = GetCurrentDirectoryA (0, null);
305                                 auto dir = new char [len];
306                                 GetCurrentDirectoryA (len, dir.ptr);
307                                 if (len)
308                                    {
309                                    if (dir[len-2] is '/')
310                                        dir.length = len-1;
311                                    else
312                                        dir[len-1] = '/'; 
313                                    path = standard (dir);
314                                    }
315                                 else
316                                    exception ("Failed to get current directory");
317                                 }
318                              else
319                                 {
320                                 wchar[MAX_PATH+2] tmp = void;
321 
322                                 auto len = GetCurrentDirectoryW (0, null);
323                                 assert (len < tmp.length);
324                                 auto dir = new char [len * 3];
325                                 GetCurrentDirectoryW (len, tmp.ptr); 
326                                 auto i = WideCharToMultiByte (CP_UTF8, 0, tmp.ptr, len, 
327                                                               cast(PCHAR)dir.ptr, dir.length, null, null);
328                                 if (len && i)
329                                    {
330                                    path = standard (dir[0..i]);
331                                    if (path[$-2] is '/')
332                                        path.length = path.length-1;
333                                    else
334                                        path[$-1] = '/';
335                                    }
336                                 else
337                                    exception ("Failed to get current directory");
338                                 }
339 
340                         return path;
341                 }
342 
343         }
344 
345         /***********************************************************************
346 
347                 Posix implementation
348 
349         ***********************************************************************/
350 
351         version (Posix)
352         {
353                 /**************************************************************
354 
355                         Returns the provided 'def' value if the variable 
356                         does not exist
357 
358                 **************************************************************/
359 
360                 static char[] get (const(char)[] variable, char[] def = null)
361                 {
362                         char* ptr = getenv ((variable ~ '\0').ptr);
363 
364                         if (ptr is null)
365                             return def;
366 
367                         return ptr[0 .. strlen(ptr)].dup;
368                 }
369 
370                 /**************************************************************
371 
372                         clears the variable, if value is null or empty
373         
374                 **************************************************************/
375 
376                 static void set (const(char)[] variable, const(char)[] value = null)
377                 {
378                         int result;
379 
380                         if (value.length is 0)
381                             unsetenv ((variable ~ '\0').ptr);
382                         else
383                            result = setenv ((variable ~ '\0').ptr, (value ~ '\0').ptr, 1);
384 
385                         if (result != 0)
386                             exception (SysError.lastMsg.idup);
387                 }
388 
389                 /**************************************************************
390 
391                         Get all set environment variables as an associative
392                         array.
393 
394                 **************************************************************/
395 
396                 static char[][char[]] get ()
397                 {
398                         char[][char[]] arr;
399 
400                         for (char** p = environ; *p; ++p)
401                             {
402                             size_t k = 0;
403                             char* str = *p;
404 
405                             while (*str++ != '=')
406                                    ++k;
407                             char[] key = (*p)[0..k];
408 
409                             k = 0;
410                             char* val = str;
411                             while (*str++)
412                                    ++k;
413                             arr[key.idup] = val[0 .. k];
414                             }
415 
416                         return arr;
417                 }
418 
419                 /**************************************************************
420 
421                         Set the current working directory
422 
423                 **************************************************************/
424 
425                 static void cwd (const(char)[] path)
426                 {
427                         char[512] tmp = void;
428                         tmp [path.length] = 0;
429                         tmp[0..path.length] = path[];
430 
431                         if (tango.stdc.posix.unistd.chdir (tmp.ptr))
432                             exception ("Failed to set current directory");
433                 }
434 
435                 /**************************************************************
436 
437                         Get the current working directory
438 
439                 **************************************************************/
440 
441                 static char[] cwd ()
442                 {
443                         char[512] tmp = void;
444 
445                         char *s = tango.stdc.posix.unistd.getcwd (tmp.ptr, tmp.length);
446                         if (s is null)
447                             exception ("Failed to get current directory");
448 
449                         auto path = s[0 .. strlen(s)+1].dup;
450                         if (path[$-2] is '/') // root path has the slash
451                             path.length = path.length-1;
452                         else
453                             path[$-1] = '/';
454                         return path;
455                 }
456         }
457 }
458 
459                 
460 /*******************************************************************************
461 
462 
463 *******************************************************************************/
464 
465 debug (Environment)
466 {
467         import tango.io.Console;
468 
469 
470         void main(const(char)[][] args)
471         {
472         enum immutable(char)[] VAR = "TESTENVVAR";
473         enum immutable(char)[] VAL1 = "VAL1";
474         enum immutable(char)[] VAL2 = "VAL2";
475 
476         assert(Environment.get(VAR) is null);
477 
478         Environment.set(VAR, VAL1);
479         assert(Environment.get(VAR) == VAL1);
480 
481         Environment.set(VAR, VAL2);
482         assert(Environment.get(VAR) == VAL2);
483 
484         Environment.set(VAR, null);
485         assert(Environment.get(VAR) is null);
486 
487         Environment.set(VAR, VAL1);
488         Environment.set(VAR, "");
489 
490         assert(Environment.get(VAR) is null);
491 
492         foreach (key, value; Environment.get)
493                  Cout (key) ("=") (value).newline;
494 
495         if (args.length > 0)
496            {
497            auto p = Environment.exePath (args[0]);
498            Cout (p).newline;
499            }
500 
501         if (args.length > 1)
502            {
503            if (auto p = Environment.exePath (args[1]))
504                Cout (p).newline;
505            }
506         }
507 }
508