]> git.mxchange.org Git - simgear.git/blob - simgear/misc/sg_path.cxx
ffc2641a26c9a08c4ba702e21de06721c7df55f8
[simgear.git] / simgear / misc / sg_path.cxx
1 // sg_path.cxx -- routines to abstract out path separator differences
2 //               between MacOS and the rest of the world
3 //
4 // Written by Curtis L. Olson, started April 1999.
5 //
6 // Copyright (C) 1999  Curtis L. Olson - http://www.flightgear.org/~curt
7 //
8 // This library is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU Library General Public
10 // License as published by the Free Software Foundation; either
11 // version 2 of the License, or (at your option) any later version.
12 //
13 // This library is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 // Library General Public License for more details.
17 //
18 // You should have received a copy of the GNU General Public License
19 // along with this program; if not, write to the Free Software
20 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
21 //
22 // $Id$
23
24
25 #include <simgear/compiler.h>
26
27 #include <simgear_config.h>
28 #include <simgear/debug/logstream.hxx>
29 #include <simgear/misc/strutils.hxx>
30 #include <simgear/misc/sgstream.hxx>
31
32 #include <stdio.h>
33 #include <sys/stat.h>
34 #include <errno.h>
35 #include <fstream>
36 #include <cstdlib>
37
38 #ifdef _WIN32
39 #  include <direct.h>
40 #endif
41 #include "sg_path.hxx"
42
43 #include <boost/algorithm/string/case_conv.hpp>
44
45 using std::string;
46 using simgear::strutils::starts_with;
47
48 /**
49  * define directory path separators
50  */
51
52 static const char sgDirPathSep = '/';
53 static const char sgDirPathSepBad = '\\';
54
55 #ifdef _WIN32
56 static const char sgSearchPathSep = ';';
57 #else
58 static const char sgSearchPathSep = ':';
59 #endif
60
61 #ifdef _WIN32
62 #include <ShlObj.h>         // for CSIDL
63 // TODO: replace this include file with the official <versionhelpers.h> header
64 // included in the Windows 8.1 SDK
65 #include "sgversionhelpers.hxx"
66
67 #define ENABLE_OLD_PATH_API 1
68
69 static SGPath pathForCSIDL(int csidl, const SGPath& def)
70 {
71         typedef BOOL (WINAPI*GetSpecialFolderPath)(HWND, LPSTR, int, BOOL);
72         static GetSpecialFolderPath SHGetSpecialFolderPath = NULL;
73
74         // lazy open+resolve of shell32
75         if (!SHGetSpecialFolderPath) {
76                 HINSTANCE shellDll = ::LoadLibrary("shell32");
77                 SHGetSpecialFolderPath = (GetSpecialFolderPath) GetProcAddress(shellDll, "SHGetSpecialFolderPathA");
78         }
79
80         if (!SHGetSpecialFolderPath){
81                 return def;
82         }
83
84         char path[MAX_PATH];
85         if (SHGetSpecialFolderPath(0, path, csidl, false)) {
86                 return SGPath(path, def.getPermissionChecker());
87         }
88
89         return def;
90 }
91
92 static SGPath pathForKnownFolder(REFKNOWNFOLDERID folderId, const SGPath& def)
93 {
94     typedef HRESULT (WINAPI*PSHGKFP)(REFKNOWNFOLDERID, DWORD, HANDLE, PWSTR*);
95
96     HINSTANCE shellDll = LoadLibrary(TEXT("shell32"));
97     if (shellDll != NULL) {
98         PSHGKFP pSHGetKnownFolderPath = (PSHGKFP) GetProcAddress(shellDll, "SHGetKnownFolderPath");
99         if (pSHGetKnownFolderPath != NULL) {
100             // system call will allocate dynamic memory... which we must release when done
101             wchar_t* localFolder = 0;
102
103             if (pSHGetKnownFolderPath(folderId, KF_FLAG_DEFAULT_PATH, NULL, &localFolder) == S_OK) {
104                 // copy into local memory
105                 char path[MAX_PATH];
106                 size_t len;
107                 if (wcstombs_s(&len, path, localFolder, MAX_PATH) != S_OK) {
108                     path[0] = '\0';
109                     SG_LOG(SG_GENERAL, SG_WARN, "WCS to MBS failed");
110                 }
111
112                 SGPath folder_path = SGPath(path, def.getPermissionChecker());
113
114                 // release dynamic memory
115                 CoTaskMemFree(static_cast<void*>(localFolder));
116
117                 return folder_path;
118             }
119         }
120
121         FreeLibrary(shellDll);
122     }
123
124     return def;
125 }
126
127 #elif __APPLE__
128
129 // defined in CocoaHelpers.mm
130 SGPath appleSpecialFolder(int dirType, int domainMask, const SGPath& def);
131
132 #else
133 static SGPath getXDGDir( const std::string& name,
134                          const SGPath& def,
135                          const std::string& fallback )
136 {
137   // http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
138
139   // $XDG_CONFIG_HOME defines the base directory relative to which user specific
140   // configuration files should be stored. If $XDG_CONFIG_HOME is either not set
141   // or empty, a default equal to $HOME/.config should be used.
142   const SGPath user_dirs = SGPath::fromEnv( "XDG_CONFIG_HOME",
143                                             SGPath::home() / ".config")
144                          / "user-dirs.dirs";
145
146   // Format is XDG_xxx_DIR="$HOME/yyy", where yyy is a shell-escaped
147   // homedir-relative path, or XDG_xxx_DIR="/yyy", where /yyy is an absolute
148   // path. No other format is supported.
149   const std::string XDG_ID = "XDG_" + name + "_DIR=\"";
150
151   sg_ifstream user_dirs_file( user_dirs );
152   std::string line;
153   while( std::getline(user_dirs_file, line).good() )
154   {
155     if( !starts_with(line, XDG_ID) || *line.rbegin() != '"' )
156       continue;
157
158     // Extract dir from XDG_<name>_DIR="<dir>"
159     line = line.substr(XDG_ID.length(), line.length() - XDG_ID.length() - 1 );
160
161     const std::string HOME = "$HOME";
162     if( starts_with(line, HOME) )
163       return SGPath::home(def)
164            / simgear::strutils::unescape(line.substr(HOME.length()));
165
166     return SGPath(line, def.getPermissionChecker());
167   }
168
169   if( def.isNull() )
170     return SGPath::home(def) / fallback;
171
172   return def;
173 }
174 #endif
175
176 // For windows, replace "\" by "/".
177 void
178 SGPath::fix()
179 {
180     string::size_type sz = path.size();
181     for ( string::size_type i = 0; i < sz; ++i ) {
182         if ( path[i] == sgDirPathSepBad ) {
183             path[i] = sgDirPathSep;
184         }
185     }
186     // drop trailing "/"
187     while ((sz>1)&&(path[sz-1]==sgDirPathSep))
188     {
189         path.resize(--sz);
190     }
191 }
192
193
194 // default constructor
195 SGPath::SGPath(PermissionChecker validator)
196     : path(""),
197     _permission_checker(validator),
198     _cached(false),
199     _rwCached(false),
200     _cacheEnabled(true)
201 {
202 }
203
204
205 // create a path based on "path"
206 SGPath::SGPath( const std::string& p, PermissionChecker validator )
207     : path(p),
208     _permission_checker(validator),
209     _cached(false),
210     _rwCached(false),
211     _cacheEnabled(true)
212 {
213     fix();
214 }
215
216 // create a path based on "path" and a "subpath"
217 SGPath::SGPath( const SGPath& p,
218                 const std::string& r,
219                 PermissionChecker validator )
220     : path(p.path),
221     _permission_checker(validator),
222     _cached(false),
223     _rwCached(false),
224     _cacheEnabled(p._cacheEnabled)
225 {
226     append(r);
227     fix();
228 }
229
230 SGPath SGPath::fromLocal8Bit(const char *name)
231 {
232     return SGPath(simgear::strutils::convertWindowsLocal8BitToUtf8(name));
233 }
234
235 SGPath SGPath::fromUtf8(const std::string& bytes, PermissionChecker p)
236 {
237     return SGPath(bytes, p);
238 }
239
240
241 SGPath::SGPath(const SGPath& p) :
242   path(p.path),
243   _permission_checker(p._permission_checker),
244   _cached(p._cached),
245   _rwCached(p._rwCached),
246   _cacheEnabled(p._cacheEnabled),
247   _canRead(p._canRead),
248   _canWrite(p._canWrite),
249   _exists(p._exists),
250   _isDir(p._isDir),
251   _isFile(p._isFile),
252   _modTime(p._modTime),
253   _size(p._size)
254 {
255 }
256
257 SGPath& SGPath::operator=(const SGPath& p)
258 {
259   path = p.path;
260   _permission_checker = p._permission_checker,
261   _cached = p._cached;
262   _rwCached = p._rwCached;
263   _cacheEnabled = p._cacheEnabled;
264   _canRead = p._canRead;
265   _canWrite = p._canWrite;
266   _exists = p._exists;
267   _isDir = p._isDir;
268   _isFile = p._isFile;
269   _modTime = p._modTime;
270   _size = p._size;
271   return *this;
272 }
273
274 // destructor
275 SGPath::~SGPath() {
276 }
277
278 #if defined(ENABLE_OLD_PATH_API)
279 // set path
280 void SGPath::set( const string& p ) {
281     path = p;
282     fix();
283     _cached = false;
284     _rwCached = false;
285 }
286 #endif
287
288 //------------------------------------------------------------------------------
289 void SGPath::setPermissionChecker(PermissionChecker validator)
290 {
291   _permission_checker = validator;
292   _rwCached = false;
293 }
294
295 //------------------------------------------------------------------------------
296 SGPath::PermissionChecker SGPath::getPermissionChecker() const
297 {
298   return _permission_checker;
299 }
300
301 //------------------------------------------------------------------------------
302 void SGPath::set_cached(bool cached)
303 {
304   _cacheEnabled = cached;
305   _cached = false;
306 }
307
308 // append another piece to the existing path
309 void SGPath::append( const string& p ) {
310     if ( path.empty() ) {
311         path = p;
312     } else {
313     if ( p[0] != sgDirPathSep ) {
314         path += sgDirPathSep;
315     }
316         path += p;
317     }
318     fix();
319     _cached = false;
320     _rwCached = false;
321 }
322
323 //------------------------------------------------------------------------------
324 SGPath SGPath::operator/( const std::string& p ) const
325 {
326   SGPath ret = *this;
327   ret.append(p);
328   return ret;
329 }
330
331 #if defined(ENABLE_OLD_PATH_API)
332 //add a new path component to the existing path string
333 void SGPath::add( const string& p ) {
334     append( sgSearchPathSep+p );
335 }
336 #endif
337
338 // concatenate a string to the end of the path without inserting a
339 // path separator
340 void SGPath::concat( const string& p ) {
341     if ( path.empty() ) {
342         path = p;
343     } else {
344         path += p;
345     }
346     fix();
347     _cached = false;
348     _rwCached = false;
349 }
350
351 std::string SGPath::local8BitStr() const
352 {
353     return simgear::strutils::convertUtf8ToWindowsLocal8Bit(path);
354 }
355
356 // Get the file part of the path (everything after the last path sep)
357 string SGPath::file() const
358 {
359     string::size_type index = path.rfind(sgDirPathSep);
360     if (index != string::npos) {
361         return path.substr(index + 1);
362     } else {
363         return path;
364     }
365 }
366
367
368 // get the directory part of the path.
369 string SGPath::dir() const {
370     int index = path.rfind(sgDirPathSep);
371     if (index >= 0) {
372         return path.substr(0, index);
373     } else {
374         return "";
375     }
376 }
377
378 SGPath SGPath::dirPath() const
379 {
380         return SGPath::fromUtf8(dir());
381 }
382
383 // get the base part of the path (everything but the extension.)
384 string SGPath::base() const
385 {
386     string::size_type index = path.rfind(".");
387     string::size_type lastSep = path.rfind(sgDirPathSep);
388
389 // tolerate dots inside directory names
390     if ((lastSep != string::npos) && (index < lastSep)) {
391         return path;
392     }
393
394     if (index != string::npos) {
395         return path.substr(0, index);
396     } else {
397         return path;
398     }
399 }
400
401 string SGPath::file_base() const
402 {
403     string::size_type index = path.rfind(sgDirPathSep);
404     if (index == string::npos) {
405         index = 0; // no separator in the name
406     } else {
407         ++index; // skip past the separator
408     }
409
410     string::size_type firstDot = path.find(".", index);
411     if (firstDot == string::npos) {
412         return path.substr(index); // no extensions
413     }
414
415     return path.substr(index, firstDot - index);
416 }
417
418 // get the extension (everything after the final ".")
419 // but make sure no "/" follows the "." character (otherwise it
420 // is has to be a directory name containing a ".").
421 string SGPath::extension() const {
422     int index = path.rfind(".");
423     if ((index >= 0)  && (path.find("/", index) == string::npos)) {
424         return path.substr(index + 1);
425     } else {
426         return "";
427     }
428 }
429
430 string SGPath::lower_extension() const {
431     return boost::to_lower_copy(extension());
432 }
433
434 string SGPath::complete_lower_extension() const
435 {
436     string::size_type index = path.rfind(sgDirPathSep);
437     if (index == string::npos) {
438         index = 0; // no separator in the name
439     } else {
440         ++index; // skip past the separator
441     }
442
443     string::size_type firstDot = path.find(".", index);
444     if ((firstDot != string::npos)  && (path.find(sgDirPathSep, firstDot) == string::npos)) {
445         return boost::to_lower_copy(path.substr(firstDot + 1));
446     } else {
447         return "";
448     }
449 }
450
451 //------------------------------------------------------------------------------
452 void SGPath::validate() const
453 {
454   if (_cached && _cacheEnabled) {
455     return;
456   }
457
458   if (path.empty()) {
459           _exists = false;
460           return;
461   }
462
463 #ifdef _WIN32
464   struct _stat buf ;
465   bool remove_trailing = false;
466   string statPath(local8BitStr());
467   if ((path.length() > 1) && (path.back() == '/')) {
468           statPath.pop_back();
469   }
470
471   if (_stat(statPath.c_str(), &buf ) < 0) {
472     _exists = false;
473   } else {
474     _exists = true;
475     _isFile = ((S_IFREG & buf.st_mode ) !=0);
476     _isDir = ((S_IFDIR & buf.st_mode ) !=0);
477     _modTime = buf.st_mtime;
478     _size = buf.st_size;
479   }
480
481 #else
482   struct stat buf ;
483
484   if (stat(path.c_str(), &buf ) < 0) {
485     _exists = false;
486   } else {
487     _exists = true;
488     _isFile = ((S_ISREG(buf.st_mode )) != 0);
489     _isDir = ((S_ISDIR(buf.st_mode )) != 0);
490     _modTime = buf.st_mtime;
491     _size = buf.st_size;
492   }
493
494 #endif
495   _cached = true;
496 }
497
498 //------------------------------------------------------------------------------
499 void SGPath::checkAccess() const
500 {
501   if( _rwCached && _cacheEnabled )
502     return;
503
504   if( _permission_checker )
505   {
506     Permissions p = _permission_checker(*this);
507     _canRead = p.read;
508     _canWrite = p.write;
509   }
510   else
511   {
512     _canRead = true;
513     _canWrite = true;
514   }
515
516   _rwCached = true;
517 }
518
519 bool SGPath::exists() const
520 {
521   validate();
522   return _exists;
523 }
524
525 //------------------------------------------------------------------------------
526 bool SGPath::canRead() const
527 {
528   checkAccess();
529   return _canRead;
530 }
531
532 //------------------------------------------------------------------------------
533 bool SGPath::canWrite() const
534 {
535   checkAccess();
536   return _canWrite;
537 }
538
539 bool SGPath::isDir() const
540 {
541   validate();
542   return _exists && _isDir;
543 }
544
545 bool SGPath::isFile() const
546 {
547   validate();
548   return _exists && _isFile;
549 }
550
551 //------------------------------------------------------------------------------
552 #ifdef _WIN32
553 #  define sgMkDir(d,m)       _mkdir(d)
554 #else
555 #  define sgMkDir(d,m)       mkdir(d,m)
556 #endif
557
558 int SGPath::create_dir(mode_t mode)
559 {
560   if( !canWrite() )
561   {
562     SG_LOG( SG_IO,
563             SG_WARN, "Error creating directory for '" << *this << "'"
564                                                     " reason: access denied" );
565     return -3;
566   }
567
568     string_list dirlist = sgPathSplit(dir());
569     if ( dirlist.empty() )
570         return -1;
571     string path = dirlist[0];
572     string_list path_elements = sgPathBranchSplit(path);
573     bool absolute = !path.empty() && path[0] == sgDirPathSep;
574
575     unsigned int i = 1;
576     SGPath dir(absolute ? string( 1, sgDirPathSep ) : "", _permission_checker);
577     dir.concat( path_elements[0] );
578         std::string ds = dir.local8BitStr();
579 #ifdef _WIN32
580     if ( ds.find(':') != string::npos && path_elements.size() >= 2 ) {
581         dir.append( path_elements[1] );
582         i = 2;
583         ds = dir.local8BitStr();
584     }
585 #endif
586   struct stat info;
587   int r;
588   for(; (r = stat(dir.c_str(), &info)) == 0 && i < path_elements.size(); ++i) {
589     dir.append(path_elements[i]);
590 }
591   if( r == 0 )
592       return 0; // Directory already exists
593
594   for(;;)
595   {
596     if( sgMkDir(dir.c_str(), mode) )
597     {
598       SG_LOG( SG_IO,
599               SG_ALERT, "Error creating directory: (" << dir << ")" );
600       return -2;
601     }
602     else
603       SG_LOG(SG_IO, SG_DEBUG, "Directory created: " << dir);
604
605     if( i >= path_elements.size() )
606       return 0;
607
608     dir.append(path_elements[i++]);
609   }
610
611   return 0;
612 }
613
614 string_list sgPathBranchSplit( const string &dirpath ) {
615     string_list path_elements;
616     string element, path = dirpath;
617     while ( ! path.empty() ) {
618         size_t p = path.find( sgDirPathSep );
619         if ( p != string::npos ) {
620             element = path.substr( 0, p );
621             path.erase( 0, p + 1 );
622         } else {
623             element = path;
624             path = "";
625         }
626         if ( ! element.empty() )
627             path_elements.push_back( element );
628     }
629     return path_elements;
630 }
631
632
633 string_list sgPathSplit( const string &search_path ) {
634     string tmp = search_path;
635     string_list result;
636     result.clear();
637
638     bool done = false;
639
640     while ( !done ) {
641         int index = tmp.find(sgSearchPathSep);
642         if (index >= 0) {
643             result.push_back( tmp.substr(0, index) );
644             tmp = tmp.substr( index + 1 );
645         } else {
646             if ( !tmp.empty() )
647                 result.push_back( tmp );
648             done = true;
649         }
650     }
651
652     return result;
653 }
654
655 bool SGPath::isAbsolute() const
656 {
657   if (path.empty()) {
658     return false;
659   }
660
661 #ifdef _WIN32
662   // detect '[A-Za-z]:/'
663   if (path.size() > 2) {
664     if (isalpha(path[0]) && (path[1] == ':') && (path[2] == sgDirPathSep)) {
665       return true;
666     }
667   }
668 #endif
669
670   return (path[0] == sgDirPathSep);
671 }
672
673 bool SGPath::isNull() const
674 {
675   return path.empty();
676 }
677
678 #if defined(ENABLE_OLD_PATH_API)
679 std::string SGPath::str_native() const
680 {
681 #ifdef _WIN32
682     std::string s = local8BitStr();
683     std::string::size_type pos;
684     std::string nativeSeparator;
685     nativeSeparator = sgDirPathSepBad;
686
687     while( (pos=s.find( sgDirPathSep )) != std::string::npos ) {
688         s.replace( pos, 1, nativeSeparator );
689     }
690     return s;
691 #else
692     return utf8Str();
693 #endif
694 }
695 #endif
696
697 //------------------------------------------------------------------------------
698 bool SGPath::remove()
699 {
700   if( !canWrite() )
701   {
702     SG_LOG( SG_IO, SG_WARN, "file remove failed: (" << *this << ")"
703                                                " reason: access denied" );
704     return false;
705   }
706
707   std::string ps = local8BitStr();
708   int err = ::unlink(ps.c_str());
709   if( err )
710   {
711     SG_LOG( SG_IO, SG_WARN, "file remove failed: (" << *this << ") "
712                                                " reason: " << strerror(errno) );
713     // TODO check if failed unlink can really change any of the cached values
714   }
715
716   _cached = false; // stat again if required
717   _rwCached = false;
718   return (err == 0);
719 }
720
721 time_t SGPath::modTime() const
722 {
723     validate();
724     return _modTime;
725 }
726
727 size_t SGPath::sizeInBytes() const
728 {
729     validate();
730     return _size;
731 }
732
733 bool SGPath::operator==(const SGPath& other) const
734 {
735     return (path == other.path);
736 }
737
738 bool SGPath::operator!=(const SGPath& other) const
739 {
740     return (path != other.path);
741 }
742
743 //------------------------------------------------------------------------------
744 bool SGPath::rename(const SGPath& newName)
745 {
746   if( !canRead() || !canWrite() || !newName.canWrite() )
747   {
748     SG_LOG( SG_IO, SG_WARN, "rename failed: from " << *this <<
749                                             " to " << newName <<
750                                             " reason: access denied" );
751     return false;
752   }
753
754 #ifdef SG_WINDOWS
755         if (newName.exists()) {
756                 SGPath r(newName);
757                 if (!r.remove()) {
758                         return false;
759                 }
760         }
761 #endif
762     std::string p = local8BitStr();
763     std::string np = newName.local8BitStr();
764
765   if( ::rename(p.c_str(), np.c_str()) != 0 )
766   {
767     SG_LOG( SG_IO, SG_WARN, "rename failed: from " << *this <<
768                                             " to " << newName <<
769                                             " reason: " << strerror(errno) );
770     return false;
771   }
772
773   path = newName.path;
774
775   // Do not remove permission checker (could happen for example if just using
776   // a std::string as new name)
777   if( newName._permission_checker )
778     _permission_checker = newName._permission_checker;
779
780   _cached = false;
781   _rwCached = false;
782
783   return true;
784 }
785
786 //------------------------------------------------------------------------------
787 SGPath SGPath::standardLocation(StandardLocation type, const SGPath& def)
788 {
789   switch(type)
790   {
791     case HOME:
792       return home(def);
793
794 #ifdef _WIN32
795     case DESKTOP:
796         if (IsWindowsVistaOrGreater())
797             return pathForKnownFolder(FOLDERID_Desktop, def);
798
799         return pathForCSIDL(CSIDL_DESKTOPDIRECTORY, def);
800
801     case DOWNLOADS:
802         if (IsWindowsVistaOrGreater())
803             return pathForKnownFolder(FOLDERID_Downloads, def);
804
805         if (!def.isNull())
806             return def;
807
808         return pathForCSIDL(CSIDL_DESKTOPDIRECTORY, def);
809
810     case DOCUMENTS:
811         if (IsWindowsVistaOrGreater())
812             return pathForKnownFolder(FOLDERID_Documents, def);
813
814         return pathForCSIDL(CSIDL_MYDOCUMENTS, def);
815
816     case PICTURES:
817         if (IsWindowsVistaOrGreater())
818             return pathForKnownFolder(FOLDERID_Pictures, def);
819
820         return pathForCSIDL(CSIDL_MYPICTURES, def);
821
822 #elif __APPLE__
823       // since this is C++, we can't include NSPathUtilities.h to access the enum
824       // values, so hard-coding them here (they are stable, don't worry)
825     case DOWNLOADS:
826       return appleSpecialFolder(15, 1, def);
827     case DESKTOP:
828       return appleSpecialFolder(12, 1, def);
829     case DOCUMENTS:
830       return appleSpecialFolder(9, 1, def);
831     case PICTURES:
832       return appleSpecialFolder(19, 1, def);
833 #else
834     case DESKTOP:
835       return getXDGDir("DESKTOP", def, "Desktop");
836     case DOWNLOADS:
837       return getXDGDir("DOWNLOADS", def, "Downloads");
838     case DOCUMENTS:
839       return getXDGDir("DOCUMENTS", def, "Documents");
840     case PICTURES:
841       return getXDGDir("PICTURES", def, "Pictures");
842 #endif
843     default:
844       SG_LOG( SG_GENERAL,
845               SG_WARN,
846               "SGPath::standardLocation() unhandled type: " << type );
847       return def;
848   }
849 }
850
851 //------------------------------------------------------------------------------
852 SGPath SGPath::fromEnv(const char* name, const SGPath& def)
853 {
854   const char* val = getenv(name);
855   if( val && val[0] )
856     return SGPath(val, def._permission_checker);
857   return def;
858 }
859
860 //------------------------------------------------------------------------------
861
862 std::vector<SGPath> SGPath::pathsFromEnv(const char *name)
863 {
864     std::vector<SGPath> r;
865     const char* val = getenv(name);
866     if (!val) {
867         return r;
868     }
869
870     string_list items =  sgPathSplit(val);
871     string_list_iterator it;
872     for (it = items.begin(); it != items.end(); ++it) {
873         r.push_back(SGPath::fromLocal8Bit(it->c_str()));
874     }
875
876     return r;
877 }
878
879 //------------------------------------------------------------------------------
880
881 std::vector<SGPath> SGPath::pathsFromUtf8(const std::string& paths)
882 {
883         std::vector<SGPath> r;
884         string_list items = sgPathSplit(paths);
885         string_list_iterator it;
886         for (it = items.begin(); it != items.end(); ++it) {
887                 r.push_back(SGPath::fromUtf8(it->c_str()));
888         }
889
890         return r;
891 }
892
893 //------------------------------------------------------------------------------
894
895 std::vector<SGPath> SGPath::pathsFromLocal8Bit(const std::string& paths)
896 {
897     std::vector<SGPath> r;
898     string_list items =  sgPathSplit(paths);
899     string_list_iterator it;
900     for (it = items.begin(); it != items.end(); ++it) {
901         r.push_back(SGPath::fromLocal8Bit(it->c_str()));
902     }
903
904     return r;
905 }
906
907 //------------------------------------------------------------------------------
908 SGPath SGPath::home(const SGPath& def)
909 {
910 #ifdef _WIN32
911     return fromEnv("USERPROFILE", def);
912 #else
913     return fromEnv("HOME", def);
914 #endif
915 }
916
917 //------------------------------------------------------------------------------
918 SGPath SGPath::desktop(const SGPath& def)
919 {
920   return standardLocation(DESKTOP, def);
921 }
922
923 //------------------------------------------------------------------------------
924 SGPath SGPath::documents(const SGPath& def)
925 {
926   return standardLocation(DOCUMENTS, def);
927 }
928
929 //------------------------------------------------------------------------------
930 SGPath SGPath::realpath() const
931 {
932 #if defined(_MSC_VER) /*for MS compilers */ || defined(_WIN32) /*needed for non MS windows compilers like MingW*/
933     // with absPath NULL, will allocate, and ignore length
934     char *buf = _fullpath( NULL, path.c_str(), _MAX_PATH );
935 #else
936     // POSIX
937     char* buf = ::realpath(path.c_str(), NULL);
938 #endif
939     if (!buf) // File does not exist: return the realpath it would have if created now
940               // (needed for fgValidatePath security)
941     {
942         if (path.empty()) {
943             return SGPath(".").realpath(); // current directory
944         }
945         std::string this_dir = dir();
946         if (isAbsolute() && this_dir.empty()) { // top level
947             this_dir = "/";
948         }
949         if (file() == "..") {
950             this_dir = SGPath(this_dir).realpath().dir();
951             if (this_dir.empty()) { // invalid path: .. above root
952                 return SGPath();
953             }
954             return SGPath(this_dir).realpath(); // use native path separator,
955                         // and handle 'existing/nonexisting/../symlink' paths
956         }
957         return SGPath(this_dir).realpath() / file();
958     }
959     SGPath p(SGPath::fromLocal8Bit(buf));
960     free(buf);
961     return p;
962 }
963
964 //------------------------------------------------------------------------------
965
966 std::string SGPath::join(const std::vector<SGPath>& paths, const std::string& joinWith)
967 {
968     std::string r;
969     if (paths.empty()) {
970         return r;
971     }
972
973     r = paths[0].utf8Str();
974     for (size_t i=1; i<paths.size(); ++i) {
975         r += joinWith + paths[i].utf8Str();
976     }
977
978     return r;
979 }
980
981 //------------------------------------------------------------------------------
982 std::wstring SGPath::wstr() const
983 {
984 #ifdef SG_WINDOWS
985         simgear::strutils::WCharVec ws = simgear::strutils::convertUtf8ToWString(path);
986         return std::wstring(ws.data(), ws.size());
987 #else
988    const size_t buflen = mbstowcs(NULL, path.c_str(), 0)+1;
989    wchar_t* wideBuf = (wchar_t*)malloc(buflen * sizeof(wchar_t));
990    if (wideBuf) {
991        size_t count = mbstowcs(wideBuf, path.c_str(), buflen);
992        if (count == (size_t)-1) {
993            return std::wstring();
994        }
995
996        std::wstring rv(wideBuf, count);
997        free(wideBuf);
998        return rv;
999    } else {
1000        SG_LOG( SG_GENERAL, SG_ALERT, "SGPath::wstr: unable to allocate enough memory for " << *this );
1001    }
1002 #endif
1003    return std::wstring();
1004 }