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