]> git.mxchange.org Git - simgear.git/blob - simgear/misc/sg_path.cxx
SGPath: add support for custom PermissionChecker
[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
59 // For windows, replace "\" by "/".
60 void
61 SGPath::fix()
62 {
63     string::size_type sz = path.size();
64     for ( string::size_type i = 0; i < sz; ++i ) {
65         if ( path[i] == sgDirPathSepBad ) {
66             path[i] = sgDirPathSep;
67         }
68     }
69     // drop trailing "/"
70     while ((sz>1)&&(path[sz-1]==sgDirPathSep))
71     {
72         path.resize(--sz);
73     }
74 }
75
76
77 // default constructor
78 SGPath::SGPath(PermissonChecker validator)
79     : path(""),
80     _permisson_checker(validator),
81     _cached(false),
82     _rwCached(false),
83     _cacheEnabled(true)
84 {
85 }
86
87
88 // create a path based on "path"
89 SGPath::SGPath( const std::string& p, PermissonChecker validator )
90     : path(p),
91     _permisson_checker(validator),
92     _cached(false),
93     _rwCached(false),
94     _cacheEnabled(true)
95 {
96     fix();
97 }
98
99 // create a path based on "path" and a "subpath"
100 SGPath::SGPath( const SGPath& p,
101                 const std::string& r,
102                 PermissonChecker validator )
103     : path(p.path),
104     _permisson_checker(validator),
105     _cached(false),
106     _rwCached(false),
107     _cacheEnabled(p._cacheEnabled)
108 {
109     append(r);
110     fix();
111 }
112
113 SGPath::SGPath(const SGPath& p) :
114   path(p.path),
115   _permisson_checker(p._permisson_checker),
116   _cached(p._cached),
117   _rwCached(p._rwCached),
118   _cacheEnabled(p._cacheEnabled),
119   _canRead(p._canRead),
120   _canWrite(p._canWrite),
121   _exists(p._exists),
122   _isDir(p._isDir),
123   _isFile(p._isFile),
124   _modTime(p._modTime)
125 {
126 }
127
128 SGPath& SGPath::operator=(const SGPath& p)
129 {
130   path = p.path;
131   _permisson_checker = p._permisson_checker,
132   _cached = p._cached;
133   _rwCached = p._rwCached;
134   _cacheEnabled = p._cacheEnabled;
135   _canRead = p._canRead;
136   _canWrite = p._canWrite;
137   _exists = p._exists;
138   _isDir = p._isDir;
139   _isFile = p._isFile;
140   _modTime = p._modTime;
141   return *this;
142 }
143
144 // destructor
145 SGPath::~SGPath() {
146 }
147
148
149 // set path
150 void SGPath::set( const string& p ) {
151     path = p;
152     fix();
153     _cached = false;
154     _rwCached = false;
155 }
156
157 //------------------------------------------------------------------------------
158 void SGPath::setPermissonChecker(PermissonChecker validator)
159 {
160   _permisson_checker = validator;
161   _rwCached = false;
162 }
163
164 //------------------------------------------------------------------------------
165 SGPath::PermissonChecker SGPath::getPermissonChecker() const
166 {
167   return _permisson_checker;
168 }
169
170 //------------------------------------------------------------------------------
171 void SGPath::set_cached(bool cached)
172 {
173   _cacheEnabled = cached;
174 }
175
176 // append another piece to the existing path
177 void SGPath::append( const string& p ) {
178     if ( path.empty() ) {
179         path = p;
180     } else {
181     if ( p[0] != sgDirPathSep ) {
182         path += sgDirPathSep;
183     }
184         path += p;
185     }
186     fix();
187     _cached = false;
188     _rwCached = false;
189 }
190
191 //------------------------------------------------------------------------------
192 SGPath SGPath::operator/( const std::string& p ) const
193 {
194   SGPath ret = *this;
195   ret.append(p);
196   return ret;
197 }
198
199 //add a new path component to the existing path string
200 void SGPath::add( const string& p ) {
201     append( sgSearchPathSep+p );
202 }
203
204
205 // concatenate a string to the end of the path without inserting a
206 // path separator
207 void SGPath::concat( const string& p ) {
208     if ( path.empty() ) {
209         path = p;
210     } else {
211         path += p;
212     }
213     fix();
214     _cached = false;
215     _rwCached = false;
216 }
217
218
219 // Get the file part of the path (everything after the last path sep)
220 string SGPath::file() const
221 {
222     string::size_type index = path.rfind(sgDirPathSep);
223     if (index != string::npos) {
224         return path.substr(index + 1);
225     } else {
226         return path;
227     }
228 }
229   
230
231 // get the directory part of the path.
232 string SGPath::dir() const {
233     int index = path.rfind(sgDirPathSep);
234     if (index >= 0) {
235         return path.substr(0, index);
236     } else {
237         return "";
238     }
239 }
240
241 // get the base part of the path (everything but the extension.)
242 string SGPath::base() const
243 {
244     string::size_type index = path.rfind(".");
245     string::size_type lastSep = path.rfind(sgDirPathSep);
246     
247 // tolerate dots inside directory names
248     if ((lastSep != string::npos) && (index < lastSep)) {
249         return path;
250     }
251     
252     if (index != string::npos) {
253         return path.substr(0, index);
254     } else {
255         return path;
256     }
257 }
258
259 string SGPath::file_base() const
260 {
261     string::size_type index = path.rfind(sgDirPathSep);
262     if (index == string::npos) {
263         index = 0; // no separator in the name
264     } else {
265         ++index; // skip past the separator
266     }
267     
268     string::size_type firstDot = path.find(".", index);
269     if (firstDot == string::npos) {
270         return path.substr(index); // no extensions
271     }
272     
273     return path.substr(index, firstDot - index);
274 }
275
276 // get the extension (everything after the final ".")
277 // but make sure no "/" follows the "." character (otherwise it
278 // is has to be a directory name containing a ".").
279 string SGPath::extension() const {
280     int index = path.rfind(".");
281     if ((index >= 0)  && (path.find("/", index) == string::npos)) {
282         return path.substr(index + 1);
283     } else {
284         return "";
285     }
286 }
287
288 string SGPath::lower_extension() const {
289     return boost::to_lower_copy(extension());
290 }
291
292 string SGPath::complete_lower_extension() const
293 {
294     string::size_type index = path.rfind(sgDirPathSep);
295     if (index == string::npos) {
296         index = 0; // no separator in the name
297     } else {
298         ++index; // skip past the separator
299     }
300     
301     string::size_type firstDot = path.find(".", index);
302     if ((firstDot != string::npos)  && (path.find(sgDirPathSep, firstDot) == string::npos)) {
303         return boost::to_lower_copy(path.substr(firstDot + 1));
304     } else {
305         return "";
306     }
307 }
308
309 void SGPath::validate() const
310 {
311   if (_cached && _cacheEnabled) {
312     return;
313   }
314
315 #ifdef _WIN32
316   struct _stat buf ;
317
318   bool remove_trailing = false;
319   if ( path.length() > 1 && path[path.length()-1] == '/' )
320       remove_trailing=true;
321   if (_stat (path.substr(0,remove_trailing?path.length()-1:path.length()).c_str(), &buf ) < 0) {
322     _exists = false;
323   } else {
324     _exists = true;
325     _isFile = ((S_IFREG & buf.st_mode ) !=0);
326     _isDir = ((S_IFDIR & buf.st_mode ) !=0);
327     _modTime = buf.st_mtime;
328   }
329
330 #else
331   struct stat buf ;
332
333   if (stat(path.c_str(), &buf ) < 0) {
334     _exists = false;
335   } else {
336     _exists = true;
337     _isFile = ((S_ISREG(buf.st_mode )) != 0);
338     _isDir = ((S_ISDIR(buf.st_mode )) != 0);
339     _modTime = buf.st_mtime;
340   }
341   
342 #endif
343   _cached = true;
344 }
345
346 void SGPath::checkAccess() const
347 {
348   if( _rwCached && _cacheEnabled )
349     return;
350
351   if( _permisson_checker )
352   {
353     Permissions p = _permisson_checker(*this);
354     _canRead = p.read;
355     _canWrite = p.write;
356   }
357   else
358   {
359     _canRead = true;
360     _canWrite = true;
361   }
362
363   _rwCached = true;
364 }
365
366 bool SGPath::exists() const
367 {
368   validate();
369   return _exists;
370 }
371
372 //------------------------------------------------------------------------------
373 bool SGPath::canRead() const
374 {
375   checkAccess();
376   return _canRead;
377 }
378
379 //------------------------------------------------------------------------------
380 bool SGPath::canWrite() const
381 {
382   checkAccess();
383   return _canWrite;
384 }
385
386 bool SGPath::isDir() const
387 {
388   validate();
389   return _exists && _isDir;
390 }
391
392 bool SGPath::isFile() const
393 {
394   validate();
395   return _exists && _isFile;
396 }
397
398 #ifdef _WIN32
399 #  define sgMkDir(d,m)       _mkdir(d)
400 #else
401 #  define sgMkDir(d,m)       mkdir(d,m)
402 #endif
403
404
405 int SGPath::create_dir( mode_t mode ) {
406     string_list dirlist = sgPathSplit(dir());
407     if ( dirlist.empty() )
408         return -1;
409     string path = dirlist[0];
410     string_list path_elements = sgPathBranchSplit(path);
411     bool absolute = !path.empty() && path[0] == sgDirPathSep;
412
413     unsigned int i = 1;
414     SGPath dir(absolute ? string( 1, sgDirPathSep ) : "", _permisson_checker);
415     dir.concat( path_elements[0] );
416 #ifdef _WIN32
417     if ( dir.str().find(':') != string::npos && path_elements.size() >= 2 ) {
418         dir.append( path_elements[1] );
419         i = 2;
420     }
421 #endif
422     struct stat info;
423     int r;
424     for(; ( r = stat( dir.c_str(), &info ) ) == 0 && i < path_elements.size(); i++) {
425         dir.append(path_elements[i]);
426     }
427     if ( r == 0 ) {
428         return 0; // Directory already exists
429     }
430     for(;;)
431     {
432       if( !dir.canWrite() )
433       {
434         SG_LOG( SG_IO,
435                 SG_ALERT, "Error creating directory: (" << dir.str() << ")" <<
436                                                    " reason: access denied" );
437         return -3;
438       }
439       else if( sgMkDir(dir.c_str(), mode) )
440       {
441         SG_LOG( SG_IO,
442                 SG_ALERT, "Error creating directory: (" << dir.str() << ")" );
443         return -2;
444       }
445
446       if( i >= path_elements.size() )
447         return  0;
448
449       dir.append(path_elements[i++]);
450     }
451
452     return 0;
453 }
454
455 string_list sgPathBranchSplit( const string &dirpath ) {
456     string_list path_elements;
457     string element, path = dirpath;
458     while ( ! path.empty() ) {
459         size_t p = path.find( sgDirPathSep );
460         if ( p != string::npos ) {
461             element = path.substr( 0, p );
462             path.erase( 0, p + 1 );
463         } else {
464             element = path;
465             path = "";
466         }
467         if ( ! element.empty() )
468             path_elements.push_back( element );
469     }
470     return path_elements;
471 }
472
473
474 string_list sgPathSplit( const string &search_path ) {
475     string tmp = search_path;
476     string_list result;
477     result.clear();
478
479     bool done = false;
480
481     while ( !done ) {
482         int index = tmp.find(sgSearchPathSep);
483         if (index >= 0) {
484             result.push_back( tmp.substr(0, index) );
485             tmp = tmp.substr( index + 1 );
486         } else {
487             if ( !tmp.empty() )
488                 result.push_back( tmp );
489             done = true;
490         }
491     }
492
493     return result;
494 }
495
496 bool SGPath::isAbsolute() const
497 {
498   if (path.empty()) {
499     return false;
500   }
501   
502 #ifdef _WIN32
503   // detect '[A-Za-z]:/'
504   if (path.size() > 2) {
505     if (isalpha(path[0]) && (path[1] == ':') && (path[2] == sgDirPathSep)) {
506       return true;
507     }
508   }
509 #endif
510   
511   return (path[0] == sgDirPathSep);
512 }
513
514 bool SGPath::isNull() const
515 {
516   return path.empty();
517 }
518
519 std::string SGPath::str_native() const
520 {
521 #ifdef _WIN32
522     std::string s = str();
523     std::string::size_type pos;
524     std::string nativeSeparator;
525     nativeSeparator = sgDirPathSepBad;
526
527     while( (pos=s.find( sgDirPathSep )) != std::string::npos ) {
528         s.replace( pos, 1, nativeSeparator );
529     }
530     return s;
531 #else
532     return str();
533 #endif
534 }
535
536 //------------------------------------------------------------------------------
537 bool SGPath::remove()
538 {
539   if( !canWrite() )
540   {
541     SG_LOG( SG_IO, SG_WARN, "file remove failed: (" << str() << ")"
542                                                " reason: access denied" );
543     return false;
544   }
545
546   int err = ::unlink(c_str());
547   if( err )
548   {
549     SG_LOG( SG_IO, SG_WARN, "file remove failed: (" << str() << ") "
550                                                " reason: " << strerror(errno) );
551     // TODO check if failed unlink can really change any of the cached values
552   }
553
554   _cached = false; // stat again if required
555   _rwCached = false;
556   return (err == 0);
557 }
558
559 time_t SGPath::modTime() const
560 {
561     validate();
562     return _modTime;
563 }
564
565 bool SGPath::operator==(const SGPath& other) const
566 {
567     return (path == other.path);
568 }
569
570 bool SGPath::operator!=(const SGPath& other) const
571 {
572     return (path != other.path);
573 }
574
575 //------------------------------------------------------------------------------
576 bool SGPath::rename(const SGPath& newName)
577 {
578   if( !newName.canWrite() )
579   {
580     SG_LOG( SG_IO, SG_WARN, "rename failed: from " << str() <<
581                                             " to " << newName.str() <<
582                                             " reason: access denied" );
583     return false;
584   }
585
586 #ifdef SG_WINDOWS
587         if (newName.exists()) {
588                 SGPath r(newName);
589                 if (!r.remove()) {
590                         return false;
591                 }
592         }
593 #endif
594   if( ::rename(c_str(), newName.c_str()) != 0 )
595   {
596     SG_LOG( SG_IO, SG_WARN, "rename failed: from " << str() <<
597                                             " to " << newName.str() <<
598                                             " reason: " << strerror(errno) );
599     return false;
600   }
601
602   path = newName.path;
603   _cached = false;
604   _rwCached = false;
605
606   return true;
607 }
608
609 //------------------------------------------------------------------------------
610 SGPath SGPath::fromEnv(const char* name, const SGPath& def)
611 {
612   const char* val = getenv(name);
613   if( val && val[0] )
614     return SGPath(val, def._permisson_checker);
615   return def;
616 }
617
618 #ifdef _WIN32
619 //------------------------------------------------------------------------------
620 SGPath SGPath::home()
621 {
622   // TODO
623   return SGPath();
624 }
625 #else
626 //------------------------------------------------------------------------------
627 SGPath SGPath::home()
628 {
629   return fromEnv("HOME");
630 }
631 #endif
632
633 #ifdef _WIN32
634
635 #include <ShlObj.h> // for CSIDL
636
637 //------------------------------------------------------------------------------
638 SGPath SGPath::desktop()
639 {
640         typedef BOOL (WINAPI*GetSpecialFolderPath)(HWND, LPSTR, int, BOOL);
641         static GetSpecialFolderPath SHGetSpecialFolderPath = NULL;
642
643         // lazy open+resolve of shell32
644         if (!SHGetSpecialFolderPath) {
645                 HINSTANCE shellDll = ::LoadLibrary("shell32");
646                 SHGetSpecialFolderPath = (GetSpecialFolderPath) GetProcAddress(shellDll, "SHGetSpecialFolderPathA");
647         }
648
649         if (!SHGetSpecialFolderPath){
650                 return SGPath();
651         }
652
653         char path[MAX_PATH];
654         if (SHGetSpecialFolderPath(0, path, CSIDL_DESKTOPDIRECTORY, false)) {
655                 return SGPath(path);
656         }
657
658         SG_LOG(SG_GENERAL, SG_ALERT, "SGPath::desktop() failed, bad" );
659         return SGPath();
660 }
661 #elif __APPLE__
662 #include <CoreServices/CoreServices.h>
663
664 //------------------------------------------------------------------------------
665 SGPath SGPath::desktop()
666 {
667   FSRef ref;
668   OSErr err = FSFindFolder(kUserDomain, kDesktopFolderType, false, &ref);
669   if (err) {
670     return SGPath();
671   }
672
673   unsigned char path[1024];
674   if (FSRefMakePath(&ref, path, 1024) != noErr) {
675     return SGPath();
676   }
677
678   return SGPath((const char*) path);
679 }
680 #else
681 //------------------------------------------------------------------------------
682 SGPath SGPath::desktop()
683 {
684   // http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
685
686   // $XDG_CONFIG_HOME defines the base directory relative to which user specific
687   // configuration files should be stored. If $XDG_CONFIG_HOME is either not set
688   // or empty, a default equal to $HOME/.config should be used.
689   const SGPath user_dirs = fromEnv("XDG_CONFIG_HOME", home() / ".config")
690                          / "user-dirs.dirs";
691
692   // Format is XDG_xxx_DIR="$HOME/yyy", where yyy is a shell-escaped
693   // homedir-relative path, or XDG_xxx_DIR="/yyy", where /yyy is an absolute
694   // path. No other format is supported.
695   const std::string DESKTOP = "XDG_DESKTOP_DIR=\"";
696
697   std::ifstream user_dirs_file( user_dirs.c_str() );
698   std::string line;
699   while( std::getline(user_dirs_file, line).good() )
700   {
701     if( !starts_with(line, DESKTOP) || *line.rbegin() != '"' )
702       continue;
703
704     // Extract dir from XDG_DESKTOP_DIR="<dir>"
705     line = line.substr(DESKTOP.length(), line.length() - DESKTOP.length() - 1 );
706
707     const std::string HOME = "$HOME";
708     if( starts_with(line, HOME) )
709       return home() / simgear::strutils::unescape(line.substr(HOME.length()));
710
711     return SGPath(line);
712   }
713
714   return home() / "Desktop";
715 }
716 #endif
717
718 std::string SGPath::realpath() const
719 {
720 #if (defined(__APPLE__) && MAC_OS_X_VERSION_MAX_ALLOWED <= 1050)
721     // Workaround for Mac OS 10.5. Somehow fgfs crashes on Mac at ::realpath. 
722     // This means relative paths cannot be used on Mac OS <= 10.5
723     return path;
724 #else
725   #if defined(_MSC_VER) /*for MS compilers */ || defined(_WIN32) /*needed for non MS windows compilers like MingW*/
726     // with absPath NULL, will allocate, and ignore length
727     char *buf = _fullpath( NULL, path.c_str(), _MAX_PATH );
728   #else
729     // POSIX
730     char* buf = ::realpath(path.c_str(), NULL);
731   #endif
732     if (!buf)
733     {
734         SG_LOG(SG_IO, SG_ALERT, "ERROR: The path '" << path << "' does not exist in the file system.");
735         return path;
736     }
737     std::string p(buf);
738     free(buf);
739     return p;
740 #endif
741 }