]> git.mxchange.org Git - simgear.git/blob - simgear/misc/sg_path.cxx
Fix #1783: repeated error message on console
[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 <stdio.h>
31 #include <sys/stat.h>
32 #include <errno.h>
33 #include <fstream>
34
35 #ifdef _WIN32
36 #  include <direct.h>
37 #endif
38 #include "sg_path.hxx"
39
40 #include <boost/algorithm/string/case_conv.hpp>
41
42 using std::string;
43 using simgear::strutils::starts_with;
44
45 /**
46  * define directory path separators
47  */
48
49 static const char sgDirPathSep = '/';
50 static const char sgDirPathSepBad = '\\';
51
52 #ifdef _WIN32
53 static const char sgSearchPathSep = ';';
54 #else
55 static const char sgSearchPathSep = ':';
56 #endif
57
58 #ifdef _WIN32
59 #include <ShlObj.h>         // for CSIDL
60 // TODO: replace this include file with the official <versionhelpers.h> header
61 // included in the Windows 8.1 SDK
62 #include "sgversionhelpers.hxx"
63
64 static SGPath pathForCSIDL(int csidl, const SGPath& def)
65 {
66         typedef BOOL (WINAPI*GetSpecialFolderPath)(HWND, LPSTR, int, BOOL);
67         static GetSpecialFolderPath SHGetSpecialFolderPath = NULL;
68
69         // lazy open+resolve of shell32
70         if (!SHGetSpecialFolderPath) {
71                 HINSTANCE shellDll = ::LoadLibrary("shell32");
72                 SHGetSpecialFolderPath = (GetSpecialFolderPath) GetProcAddress(shellDll, "SHGetSpecialFolderPathA");
73         }
74
75         if (!SHGetSpecialFolderPath){
76                 return def;
77         }
78
79         char path[MAX_PATH];
80         if (SHGetSpecialFolderPath(0, path, csidl, false)) {
81                 return SGPath(path, def.getPermissionChecker());
82         }
83
84         return def;
85 }
86
87 static SGPath pathForKnownFolder(REFKNOWNFOLDERID folderId, const SGPath& def)
88 {
89     typedef HRESULT (WINAPI*PSHGKFP)(REFKNOWNFOLDERID, DWORD, HANDLE, PWSTR*);
90
91     HINSTANCE shellDll = LoadLibrary(TEXT("shell32"));
92     if (shellDll != NULL) {
93         PSHGKFP pSHGetKnownFolderPath = (PSHGKFP) GetProcAddress(shellDll, "SHGetKnownFolderPath");
94         if (pSHGetKnownFolderPath != NULL) {
95             // system call will allocate dynamic memory... which we must release when done
96             wchar_t* localFolder = 0;
97
98             if (pSHGetKnownFolderPath(folderId, KF_FLAG_DEFAULT_PATH, NULL, &localFolder) == S_OK) {
99                 // copy into local memory
100                 char path[MAX_PATH];
101                 size_t len;
102                 if (wcstombs_s(&len, path, localFolder, MAX_PATH) != S_OK) {
103                     path[0] = '\0';
104                     SG_LOG(SG_GENERAL, SG_WARN, "WCS to MBS failed");
105                 }
106
107                 SGPath folder_path = SGPath(path, def.getPermissionChecker());
108
109                 // release dynamic memory
110                 CoTaskMemFree(static_cast<void*>(localFolder));
111
112                 return folder_path;
113             }
114         }
115
116         FreeLibrary(shellDll);
117     }
118
119     return def;
120 }
121
122 #elif __APPLE__
123
124 // defined in CocoaHelpers.mm
125 SGPath appleSpecialFolder(int dirType, int domainMask, const SGPath& def);
126
127 #else
128 static SGPath getXDGDir( const std::string& name,
129                          const SGPath& def,
130                          const std::string& fallback )
131 {
132   // http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
133
134   // $XDG_CONFIG_HOME defines the base directory relative to which user specific
135   // configuration files should be stored. If $XDG_CONFIG_HOME is either not set
136   // or empty, a default equal to $HOME/.config should be used.
137   const SGPath user_dirs = SGPath::fromEnv( "XDG_CONFIG_HOME",
138                                             SGPath::home() / ".config")
139                          / "user-dirs.dirs";
140
141   // Format is XDG_xxx_DIR="$HOME/yyy", where yyy is a shell-escaped
142   // homedir-relative path, or XDG_xxx_DIR="/yyy", where /yyy is an absolute
143   // path. No other format is supported.
144   const std::string XDG_ID = "XDG_" + name + "_DIR=\"";
145
146   std::ifstream user_dirs_file( user_dirs.c_str() );
147   std::string line;
148   while( std::getline(user_dirs_file, line).good() )
149   {
150     if( !starts_with(line, XDG_ID) || *line.rbegin() != '"' )
151       continue;
152
153     // Extract dir from XDG_<name>_DIR="<dir>"
154     line = line.substr(XDG_ID.length(), line.length() - XDG_ID.length() - 1 );
155
156     const std::string HOME = "$HOME";
157     if( starts_with(line, HOME) )
158       return SGPath::home(def)
159            / simgear::strutils::unescape(line.substr(HOME.length()));
160
161     return SGPath(line, def.getPermissionChecker());
162   }
163
164   if( def.isNull() )
165     return SGPath::home(def) / fallback;
166
167   return def;
168 }
169 #endif
170
171 // For windows, replace "\" by "/".
172 void
173 SGPath::fix()
174 {
175     string::size_type sz = path.size();
176     for ( string::size_type i = 0; i < sz; ++i ) {
177         if ( path[i] == sgDirPathSepBad ) {
178             path[i] = sgDirPathSep;
179         }
180     }
181     // drop trailing "/"
182     while ((sz>1)&&(path[sz-1]==sgDirPathSep))
183     {
184         path.resize(--sz);
185     }
186 }
187
188
189 // default constructor
190 SGPath::SGPath(PermissionChecker validator)
191     : path(""),
192     _permission_checker(validator),
193     _cached(false),
194     _rwCached(false),
195     _cacheEnabled(true)
196 {
197 }
198
199
200 // create a path based on "path"
201 SGPath::SGPath( const std::string& p, PermissionChecker validator )
202     : path(p),
203     _permission_checker(validator),
204     _cached(false),
205     _rwCached(false),
206     _cacheEnabled(true)
207 {
208     fix();
209 }
210
211 // create a path based on "path" and a "subpath"
212 SGPath::SGPath( const SGPath& p,
213                 const std::string& r,
214                 PermissionChecker validator )
215     : path(p.path),
216     _permission_checker(validator),
217     _cached(false),
218     _rwCached(false),
219     _cacheEnabled(p._cacheEnabled)
220 {
221     append(r);
222     fix();
223 }
224
225 SGPath::SGPath(const SGPath& p) :
226   path(p.path),
227   _permission_checker(p._permission_checker),
228   _cached(p._cached),
229   _rwCached(p._rwCached),
230   _cacheEnabled(p._cacheEnabled),
231   _canRead(p._canRead),
232   _canWrite(p._canWrite),
233   _exists(p._exists),
234   _isDir(p._isDir),
235   _isFile(p._isFile),
236   _modTime(p._modTime)
237 {
238 }
239
240 SGPath& SGPath::operator=(const SGPath& p)
241 {
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   return *this;
254 }
255
256 // destructor
257 SGPath::~SGPath() {
258 }
259
260
261 // set path
262 void SGPath::set( const string& p ) {
263     path = p;
264     fix();
265     _cached = false;
266     _rwCached = false;
267 }
268
269 //------------------------------------------------------------------------------
270 void SGPath::setPermissionChecker(PermissionChecker validator)
271 {
272   _permission_checker = validator;
273   _rwCached = false;
274 }
275
276 //------------------------------------------------------------------------------
277 SGPath::PermissionChecker SGPath::getPermissionChecker() const
278 {
279   return _permission_checker;
280 }
281
282 //------------------------------------------------------------------------------
283 void SGPath::set_cached(bool cached)
284 {
285   _cacheEnabled = cached;
286 }
287
288 // append another piece to the existing path
289 void SGPath::append( const string& p ) {
290     if ( path.empty() ) {
291         path = p;
292     } else {
293     if ( p[0] != sgDirPathSep ) {
294         path += sgDirPathSep;
295     }
296         path += p;
297     }
298     fix();
299     _cached = false;
300     _rwCached = false;
301 }
302
303 //------------------------------------------------------------------------------
304 SGPath SGPath::operator/( const std::string& p ) const
305 {
306   SGPath ret = *this;
307   ret.append(p);
308   return ret;
309 }
310
311 //add a new path component to the existing path string
312 void SGPath::add( const string& p ) {
313     append( sgSearchPathSep+p );
314 }
315
316
317 // concatenate a string to the end of the path without inserting a
318 // path separator
319 void SGPath::concat( const string& p ) {
320     if ( path.empty() ) {
321         path = p;
322     } else {
323         path += p;
324     }
325     fix();
326     _cached = false;
327     _rwCached = false;
328 }
329
330
331 // Get the file part of the path (everything after the last path sep)
332 string SGPath::file() const
333 {
334     string::size_type index = path.rfind(sgDirPathSep);
335     if (index != string::npos) {
336         return path.substr(index + 1);
337     } else {
338         return path;
339     }
340 }
341   
342
343 // get the directory part of the path.
344 string SGPath::dir() const {
345     int index = path.rfind(sgDirPathSep);
346     if (index >= 0) {
347         return path.substr(0, index);
348     } else {
349         return "";
350     }
351 }
352
353 // get the base part of the path (everything but the extension.)
354 string SGPath::base() const
355 {
356     string::size_type index = path.rfind(".");
357     string::size_type lastSep = path.rfind(sgDirPathSep);
358     
359 // tolerate dots inside directory names
360     if ((lastSep != string::npos) && (index < lastSep)) {
361         return path;
362     }
363     
364     if (index != string::npos) {
365         return path.substr(0, index);
366     } else {
367         return path;
368     }
369 }
370
371 string SGPath::file_base() const
372 {
373     string::size_type index = path.rfind(sgDirPathSep);
374     if (index == string::npos) {
375         index = 0; // no separator in the name
376     } else {
377         ++index; // skip past the separator
378     }
379     
380     string::size_type firstDot = path.find(".", index);
381     if (firstDot == string::npos) {
382         return path.substr(index); // no extensions
383     }
384     
385     return path.substr(index, firstDot - index);
386 }
387
388 // get the extension (everything after the final ".")
389 // but make sure no "/" follows the "." character (otherwise it
390 // is has to be a directory name containing a ".").
391 string SGPath::extension() const {
392     int index = path.rfind(".");
393     if ((index >= 0)  && (path.find("/", index) == string::npos)) {
394         return path.substr(index + 1);
395     } else {
396         return "";
397     }
398 }
399
400 string SGPath::lower_extension() const {
401     return boost::to_lower_copy(extension());
402 }
403
404 string SGPath::complete_lower_extension() const
405 {
406     string::size_type index = path.rfind(sgDirPathSep);
407     if (index == string::npos) {
408         index = 0; // no separator in the name
409     } else {
410         ++index; // skip past the separator
411     }
412     
413     string::size_type firstDot = path.find(".", index);
414     if ((firstDot != string::npos)  && (path.find(sgDirPathSep, firstDot) == string::npos)) {
415         return boost::to_lower_copy(path.substr(firstDot + 1));
416     } else {
417         return "";
418     }
419 }
420
421 //------------------------------------------------------------------------------
422 void SGPath::validate() const
423 {
424   if (_cached && _cacheEnabled) {
425     return;
426   }
427
428   if (path.empty()) {
429           _exists = false;
430           return;
431   }
432
433 #ifdef _WIN32
434   struct _stat buf ;
435   bool remove_trailing = false;
436   string statPath(path);
437   if ((path.length() > 1) && (path.back() == '/')) {
438           statPath.pop_back();
439   }
440       
441   if (_stat(statPath.c_str(), &buf ) < 0) {
442     _exists = false;
443   } else {
444     _exists = true;
445     _isFile = ((S_IFREG & buf.st_mode ) !=0);
446     _isDir = ((S_IFDIR & buf.st_mode ) !=0);
447     _modTime = buf.st_mtime;
448   }
449
450 #else
451   struct stat buf ;
452
453   if (stat(path.c_str(), &buf ) < 0) {
454     _exists = false;
455   } else {
456     _exists = true;
457     _isFile = ((S_ISREG(buf.st_mode )) != 0);
458     _isDir = ((S_ISDIR(buf.st_mode )) != 0);
459     _modTime = buf.st_mtime;
460   }
461   
462 #endif
463   _cached = true;
464 }
465
466 //------------------------------------------------------------------------------
467 void SGPath::checkAccess() const
468 {
469   if( _rwCached && _cacheEnabled )
470     return;
471
472   if( _permission_checker )
473   {
474     Permissions p = _permission_checker(*this);
475     _canRead = p.read;
476     _canWrite = p.write;
477   }
478   else
479   {
480     _canRead = true;
481     _canWrite = true;
482   }
483
484   _rwCached = true;
485 }
486
487 bool SGPath::exists() const
488 {
489   validate();
490   return _exists;
491 }
492
493 //------------------------------------------------------------------------------
494 bool SGPath::canRead() const
495 {
496   checkAccess();
497   return _canRead;
498 }
499
500 //------------------------------------------------------------------------------
501 bool SGPath::canWrite() const
502 {
503   checkAccess();
504   return _canWrite;
505 }
506
507 bool SGPath::isDir() const
508 {
509   validate();
510   return _exists && _isDir;
511 }
512
513 bool SGPath::isFile() const
514 {
515   validate();
516   return _exists && _isFile;
517 }
518
519 //------------------------------------------------------------------------------
520 #ifdef _WIN32
521 #  define sgMkDir(d,m)       _mkdir(d)
522 #else
523 #  define sgMkDir(d,m)       mkdir(d,m)
524 #endif
525
526 int SGPath::create_dir(mode_t mode)
527 {
528   if( !canWrite() )
529   {
530     SG_LOG( SG_IO,
531             SG_WARN, "Error creating directory for '" << str() << "'"
532                                                     " reason: access denied" );
533     return -3;
534   }
535
536     string_list dirlist = sgPathSplit(dir());
537     if ( dirlist.empty() )
538         return -1;
539     string path = dirlist[0];
540     string_list path_elements = sgPathBranchSplit(path);
541     bool absolute = !path.empty() && path[0] == sgDirPathSep;
542
543     unsigned int i = 1;
544     SGPath dir(absolute ? string( 1, sgDirPathSep ) : "", _permission_checker);
545     dir.concat( path_elements[0] );
546 #ifdef _WIN32
547     if ( dir.str().find(':') != string::npos && path_elements.size() >= 2 ) {
548         dir.append( path_elements[1] );
549         i = 2;
550     }
551 #endif
552   struct stat info;
553   int r;
554   for(; (r = stat(dir.c_str(), &info)) == 0 && i < path_elements.size(); ++i)
555     dir.append(path_elements[i]);
556   if( r == 0 )
557       return 0; // Directory already exists
558
559   for(;;)
560   {
561     if( sgMkDir(dir.c_str(), mode) )
562     {
563       SG_LOG( SG_IO,
564               SG_ALERT, "Error creating directory: (" << dir.str() << ")" );
565       return -2;
566     }
567     else
568       SG_LOG(SG_IO, SG_DEBUG, "Directory created: " << dir.str());
569
570     if( i >= path_elements.size() )
571       return 0;
572
573     dir.append(path_elements[i++]);
574   }
575
576   return 0;
577 }
578
579 string_list sgPathBranchSplit( const string &dirpath ) {
580     string_list path_elements;
581     string element, path = dirpath;
582     while ( ! path.empty() ) {
583         size_t p = path.find( sgDirPathSep );
584         if ( p != string::npos ) {
585             element = path.substr( 0, p );
586             path.erase( 0, p + 1 );
587         } else {
588             element = path;
589             path = "";
590         }
591         if ( ! element.empty() )
592             path_elements.push_back( element );
593     }
594     return path_elements;
595 }
596
597
598 string_list sgPathSplit( const string &search_path ) {
599     string tmp = search_path;
600     string_list result;
601     result.clear();
602
603     bool done = false;
604
605     while ( !done ) {
606         int index = tmp.find(sgSearchPathSep);
607         if (index >= 0) {
608             result.push_back( tmp.substr(0, index) );
609             tmp = tmp.substr( index + 1 );
610         } else {
611             if ( !tmp.empty() )
612                 result.push_back( tmp );
613             done = true;
614         }
615     }
616
617     return result;
618 }
619
620 bool SGPath::isAbsolute() const
621 {
622   if (path.empty()) {
623     return false;
624   }
625   
626 #ifdef _WIN32
627   // detect '[A-Za-z]:/'
628   if (path.size() > 2) {
629     if (isalpha(path[0]) && (path[1] == ':') && (path[2] == sgDirPathSep)) {
630       return true;
631     }
632   }
633 #endif
634   
635   return (path[0] == sgDirPathSep);
636 }
637
638 bool SGPath::isNull() const
639 {
640   return path.empty();
641 }
642
643 std::string SGPath::str_native() const
644 {
645 #ifdef _WIN32
646     std::string s = str();
647     std::string::size_type pos;
648     std::string nativeSeparator;
649     nativeSeparator = sgDirPathSepBad;
650
651     while( (pos=s.find( sgDirPathSep )) != std::string::npos ) {
652         s.replace( pos, 1, nativeSeparator );
653     }
654     return s;
655 #else
656     return str();
657 #endif
658 }
659
660 //------------------------------------------------------------------------------
661 bool SGPath::remove()
662 {
663   if( !canWrite() )
664   {
665     SG_LOG( SG_IO, SG_WARN, "file remove failed: (" << str() << ")"
666                                                " reason: access denied" );
667     return false;
668   }
669
670   int err = ::unlink(c_str());
671   if( err )
672   {
673     SG_LOG( SG_IO, SG_WARN, "file remove failed: (" << str() << ") "
674                                                " reason: " << strerror(errno) );
675     // TODO check if failed unlink can really change any of the cached values
676   }
677
678   _cached = false; // stat again if required
679   _rwCached = false;
680   return (err == 0);
681 }
682
683 time_t SGPath::modTime() const
684 {
685     validate();
686     return _modTime;
687 }
688
689 bool SGPath::operator==(const SGPath& other) const
690 {
691     return (path == other.path);
692 }
693
694 bool SGPath::operator!=(const SGPath& other) const
695 {
696     return (path != other.path);
697 }
698
699 //------------------------------------------------------------------------------
700 bool SGPath::rename(const SGPath& newName)
701 {
702   if( !canRead() || !canWrite() || !newName.canWrite() )
703   {
704     SG_LOG( SG_IO, SG_WARN, "rename failed: from " << str() <<
705                                             " to " << newName.str() <<
706                                             " reason: access denied" );
707     return false;
708   }
709
710 #ifdef SG_WINDOWS
711         if (newName.exists()) {
712                 SGPath r(newName);
713                 if (!r.remove()) {
714                         return false;
715                 }
716         }
717 #endif
718   if( ::rename(c_str(), newName.c_str()) != 0 )
719   {
720     SG_LOG( SG_IO, SG_WARN, "rename failed: from " << str() <<
721                                             " to " << newName.str() <<
722                                             " reason: " << strerror(errno) );
723     return false;
724   }
725
726   path = newName.path;
727
728   // Do not remove permission checker (could happen for example if just using
729   // a std::string as new name)
730   if( newName._permission_checker )
731     _permission_checker = newName._permission_checker;
732
733   _cached = false;
734   _rwCached = false;
735
736   return true;
737 }
738
739 //------------------------------------------------------------------------------
740 SGPath SGPath::standardLocation(StandardLocation type, const SGPath& def)
741 {
742   switch(type)
743   {
744     case HOME:
745       return home(def);
746
747 #ifdef _WIN32
748     case DESKTOP:
749         if (IsWindowsVistaOrGreater())
750             return pathForKnownFolder(FOLDERID_Desktop, def);
751
752         return pathForCSIDL(CSIDL_DESKTOPDIRECTORY, def);
753
754     case DOWNLOADS:
755         if (IsWindowsVistaOrGreater())
756             return pathForKnownFolder(FOLDERID_Downloads, def);
757
758         if (!def.isNull())
759             return def;
760
761         return pathForCSIDL(CSIDL_DESKTOPDIRECTORY, def);
762
763     case DOCUMENTS:
764         if (IsWindowsVistaOrGreater())
765             return pathForKnownFolder(FOLDERID_Documents, def);
766
767         return pathForCSIDL(CSIDL_MYDOCUMENTS, def);
768
769     case PICTURES:
770         if (IsWindowsVistaOrGreater())
771             return pathForKnownFolder(FOLDERID_Pictures, def);
772
773         return pathForCSIDL(CSIDL_MYPICTURES, def);
774
775 #elif __APPLE__
776       // since this is C++, we can't include NSPathUtilities.h to access the enum
777       // values, so hard-coding them here (they are stable, don't worry)
778     case DOWNLOADS:
779       return appleSpecialFolder(15, 1, def);
780     case DESKTOP:
781       return appleSpecialFolder(12, 1, def);
782     case DOCUMENTS:
783       return appleSpecialFolder(9, 1, def);
784     case PICTURES:
785       return appleSpecialFolder(19, 1, def);
786 #else
787     case DESKTOP:
788       return getXDGDir("DESKTOP", def, "Desktop");
789     case DOWNLOADS:
790       return getXDGDir("DOWNLOADS", def, "Downloads");
791     case DOCUMENTS:
792       return getXDGDir("DOCUMENTS", def, "Documents");
793     case PICTURES:
794       return getXDGDir("PICTURES", def, "Pictures");
795 #endif
796     default:
797       SG_LOG( SG_GENERAL,
798               SG_WARN,
799               "SGPath::standardLocation() unhandled type: " << type );
800       return def;
801   }
802 }
803
804 //------------------------------------------------------------------------------
805 SGPath SGPath::fromEnv(const char* name, const SGPath& def)
806 {
807   const char* val = getenv(name);
808   if( val && val[0] )
809     return SGPath(val, def._permission_checker);
810   return def;
811 }
812
813 //------------------------------------------------------------------------------
814 SGPath SGPath::home(const SGPath& def)
815 {
816 #ifdef _WIN32
817     return fromEnv("USERPROFILE", def);
818 #else
819     return fromEnv("HOME", def);
820 #endif
821 }
822
823 //------------------------------------------------------------------------------
824 SGPath SGPath::desktop(const SGPath& def)
825 {
826   return standardLocation(DESKTOP, def);
827 }
828
829 //------------------------------------------------------------------------------
830 SGPath SGPath::documents(const SGPath& def)
831 {
832   return standardLocation(DOCUMENTS, def);
833 }
834
835 //------------------------------------------------------------------------------
836 std::string SGPath::realpath() const
837 {
838 #if (defined(__APPLE__) && MAC_OS_X_VERSION_MAX_ALLOWED <= 1050)
839     // Workaround for Mac OS 10.5. Somehow fgfs crashes on Mac at ::realpath. 
840     // This means relative paths cannot be used on Mac OS <= 10.5
841     return path;
842 #else
843   #if defined(_MSC_VER) /*for MS compilers */ || defined(_WIN32) /*needed for non MS windows compilers like MingW*/
844     // with absPath NULL, will allocate, and ignore length
845     char *buf = _fullpath( NULL, path.c_str(), _MAX_PATH );
846   #else
847     // POSIX
848     char* buf = ::realpath(path.c_str(), NULL);
849   #endif
850     if (!buf)
851     {
852         SG_LOG(SG_IO, SG_WARN, "ERROR: The path '" << path << "' does not exist in the file system.");
853         return path;
854     }
855     std::string p(buf);
856     free(buf);
857     return p;
858 #endif
859 }