]> git.mxchange.org Git - simgear.git/blob - simgear/misc/sg_path.cxx
#599: Don't crash when a path does not exist.
[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 <stdio.h>
30 #include <sys/stat.h>
31 #include <errno.h>
32
33 #ifdef _WIN32
34 #  include <direct.h>
35 #endif
36 #include "sg_path.hxx"
37
38 #include <boost/algorithm/string/case_conv.hpp>
39
40 using std::string;
41
42 /**
43  * define directory path separators
44  */
45
46 static const char sgDirPathSep = '/';
47 static const char sgDirPathSepBad = '\\';
48
49 #ifdef _WIN32
50 static const char sgSearchPathSep = ';';
51 #else
52 static const char sgSearchPathSep = ':';
53 #endif
54
55
56 // For windows, replace "\" by "/".
57 void
58 SGPath::fix()
59 {
60     string::size_type sz = path.size();
61     for ( string::size_type i = 0; i < sz; ++i ) {
62         if ( path[i] == sgDirPathSepBad ) {
63             path[i] = sgDirPathSep;
64         }
65     }
66     // drop trailing "/"
67     while ((sz>1)&&(path[sz-1]==sgDirPathSep))
68     {
69         path.resize(--sz);
70     }
71 }
72
73
74 // default constructor
75 SGPath::SGPath()
76     : path(""),
77     _cached(false),
78     _cacheEnabled(true)
79 {
80 }
81
82
83 // create a path based on "path"
84 SGPath::SGPath( const std::string& p )
85     : path(p),
86     _cached(false),
87     _cacheEnabled(true)
88 {
89     fix();
90 }
91
92 // create a path based on "path" and a "subpath"
93 SGPath::SGPath( const SGPath& p, const std::string& r )
94     : path(p.path),
95     _cached(false),
96     _cacheEnabled(p._cacheEnabled)
97 {
98     append(r);
99     fix();
100 }
101
102 SGPath::SGPath(const SGPath& p) :
103   path(p.path),
104   _cached(p._cached),
105   _cacheEnabled(p._cacheEnabled),
106   _exists(p._exists),
107   _isDir(p._isDir),
108   _isFile(p._isFile),
109   _modTime(p._modTime)
110 {
111 }
112
113 SGPath& SGPath::operator=(const SGPath& p)
114 {
115   path = p.path;
116   _cached = p._cached;
117   _cacheEnabled = p._cacheEnabled;
118   _exists = p._exists;
119   _isDir = p._isDir;
120   _isFile = p._isFile;
121   _modTime = p._modTime;
122   return *this;
123 }
124
125 // destructor
126 SGPath::~SGPath() {
127 }
128
129
130 // set path
131 void SGPath::set( const string& p ) {
132     path = p;
133     fix();
134     _cached = false;
135 }
136
137 void SGPath::set_cached(bool cached)
138 {
139     _cacheEnabled = cached;
140 }
141
142 // append another piece to the existing path
143 void SGPath::append( const string& p ) {
144     if ( path.size() == 0 ) {
145         path = p;
146     } else {
147     if ( p[0] != sgDirPathSep ) {
148         path += sgDirPathSep;
149     }
150         path += p;
151     }
152     fix();
153     _cached = false;
154 }
155
156 //add a new path component to the existing path string
157 void SGPath::add( const string& p ) {
158     append( sgSearchPathSep+p );
159 }
160
161
162 // concatenate a string to the end of the path without inserting a
163 // path separator
164 void SGPath::concat( const string& p ) {
165     if ( path.size() == 0 ) {
166         path = p;
167     } else {
168         path += p;
169     }
170     fix();
171     _cached = false;
172 }
173
174
175 // Get the file part of the path (everything after the last path sep)
176 string SGPath::file() const
177 {
178     string::size_type index = path.rfind(sgDirPathSep);
179     if (index != string::npos) {
180         return path.substr(index + 1);
181     } else {
182         return path;
183     }
184 }
185   
186
187 // get the directory part of the path.
188 string SGPath::dir() const {
189     int index = path.rfind(sgDirPathSep);
190     if (index >= 0) {
191         return path.substr(0, index);
192     } else {
193         return "";
194     }
195 }
196
197 // get the base part of the path (everything but the extension.)
198 string SGPath::base() const
199 {
200     string::size_type index = path.rfind(".");
201     string::size_type lastSep = path.rfind(sgDirPathSep);
202     
203 // tolerate dots inside directory names
204     if ((lastSep != string::npos) && (index < lastSep)) {
205         return path;
206     }
207     
208     if (index != string::npos) {
209         return path.substr(0, index);
210     } else {
211         return path;
212     }
213 }
214
215 string SGPath::file_base() const
216 {
217     string::size_type index = path.rfind(sgDirPathSep);
218     if (index == string::npos) {
219         index = 0; // no separator in the name
220     } else {
221         ++index; // skip past the separator
222     }
223     
224     string::size_type firstDot = path.find(".", index);
225     if (firstDot == string::npos) {
226         return path.substr(index); // no extensions
227     }
228     
229     return path.substr(index, firstDot - index);
230 }
231
232 // get the extension (everything after the final ".")
233 // but make sure no "/" follows the "." character (otherwise it
234 // is has to be a directory name containing a ".").
235 string SGPath::extension() const {
236     int index = path.rfind(".");
237     if ((index >= 0)  && (path.find("/", index) == string::npos)) {
238         return path.substr(index + 1);
239     } else {
240         return "";
241     }
242 }
243
244 string SGPath::lower_extension() const {
245     return boost::to_lower_copy(extension());
246 }
247
248 string SGPath::complete_lower_extension() const
249 {
250     string::size_type index = path.rfind(sgDirPathSep);
251     if (index == string::npos) {
252         index = 0; // no separator in the name
253     } else {
254         ++index; // skip past the separator
255     }
256     
257     string::size_type firstDot = path.find(".", index);
258     if ((firstDot != string::npos)  && (path.find(sgDirPathSep, firstDot) == string::npos)) {
259         return boost::to_lower_copy(path.substr(firstDot + 1));
260     } else {
261         return "";
262     }
263 }
264
265 void SGPath::validate() const
266 {
267   if (_cached && _cacheEnabled) {
268     return;
269   }
270   
271 #ifdef _WIN32
272   struct _stat buf ;
273
274   bool remove_trailing = false;
275   if ( path.length() > 1 && path[path.length()-1] == '/' )
276       remove_trailing=true;
277   if (_stat (path.substr(0,remove_trailing?path.length()-1:path.length()).c_str(), &buf ) < 0) {
278     _exists = false;
279   } else {
280     _exists = true;
281     _isFile = ((S_IFREG & buf.st_mode ) !=0);
282     _isDir = ((S_IFDIR & buf.st_mode ) !=0);
283     _modTime = buf.st_mtime;
284   }
285
286 #else
287   struct stat buf ;
288
289   if (stat(path.c_str(), &buf ) < 0) {
290     _exists = false;
291   } else {
292     _exists = true;
293     _isFile = ((S_ISREG(buf.st_mode )) != 0);
294     _isDir = ((S_ISDIR(buf.st_mode )) != 0);
295     _modTime = buf.st_mtime;
296   }
297   
298 #endif
299   _cached = true;
300 }
301
302 bool SGPath::exists() const
303 {
304   validate();
305   return _exists;
306 }
307
308 bool SGPath::isDir() const
309 {
310   validate();
311   return _exists && _isDir;
312 }
313
314 bool SGPath::isFile() const
315 {
316   validate();
317   return _exists && _isFile;
318 }
319
320 #ifdef _WIN32
321 #  define sgMkDir(d,m)       _mkdir(d)
322 #else
323 #  define sgMkDir(d,m)       mkdir(d,m)
324 #endif
325
326
327 int SGPath::create_dir( mode_t mode ) {
328     string_list dirlist = sgPathSplit(dir());
329     if ( dirlist.empty() )
330         return -1;
331     string path = dirlist[0];
332     string_list path_elements = sgPathBranchSplit(path);
333     bool absolute = !path.empty() && path[0] == sgDirPathSep;
334
335     unsigned int i = 1;
336     SGPath dir = absolute ? string( 1, sgDirPathSep ) : "";
337     dir.concat( path_elements[0] );
338 #ifdef _WIN32
339     if ( dir.str().find(':') != string::npos && path_elements.size() >= 2 ) {
340         dir.append( path_elements[1] );
341         i = 2;
342     }
343 #endif
344     struct stat info;
345     int r;
346     for(; ( r = stat( dir.c_str(), &info ) ) == 0 && i < path_elements.size(); i++) {
347         dir.append(path_elements[i]);
348     }
349     if ( r == 0 ) {
350         return 0; // Directory already exists
351     }
352     if ( sgMkDir( dir.c_str(), mode) ) {
353         SG_LOG( SG_IO, SG_ALERT, "Error creating directory: " + dir.str() );
354         return -2;
355     }
356     for(; i < path_elements.size(); i++) {
357         dir.append(path_elements[i]);
358         if ( sgMkDir( dir.c_str(), mode) ) {
359             SG_LOG( SG_IO, SG_ALERT, "Error creating directory: " + dir.str() );
360             return -2;
361         }
362     }
363
364     return 0;
365 }
366
367 string_list sgPathBranchSplit( const string &dirpath ) {
368     string_list path_elements;
369     string element, path = dirpath;
370     while ( path.size() ) {
371         size_t p = path.find( sgDirPathSep );
372         if ( p != string::npos ) {
373             element = path.substr( 0, p );
374             path.erase( 0, p + 1 );
375         } else {
376             element = path;
377             path = "";
378         }
379         if ( element.size() )
380             path_elements.push_back( element );
381     }
382     return path_elements;
383 }
384
385
386 string_list sgPathSplit( const string &search_path ) {
387     string tmp = search_path;
388     string_list result;
389     result.clear();
390
391     bool done = false;
392
393     while ( !done ) {
394         int index = tmp.find(sgSearchPathSep);
395         if (index >= 0) {
396             result.push_back( tmp.substr(0, index) );
397             tmp = tmp.substr( index + 1 );
398         } else {
399             if ( !tmp.empty() )
400                 result.push_back( tmp );
401             done = true;
402         }
403     }
404
405     return result;
406 }
407
408 bool SGPath::isAbsolute() const
409 {
410   if (path.empty()) {
411     return false;
412   }
413   
414 #ifdef _WIN32
415   // detect '[A-Za-z]:/'
416   if (path.size() > 2) {
417     if (isalpha(path[0]) && (path[1] == ':') && (path[2] == sgDirPathSep)) {
418       return true;
419     }
420   }
421 #endif
422   
423   return (path[0] == sgDirPathSep);
424 }
425
426 bool SGPath::isNull() const
427 {
428   return path.empty() || (path == "");
429 }
430
431 std::string SGPath::str_native() const
432 {
433 #ifdef _WIN32
434     std::string s = str();
435     std::string::size_type pos;
436     std::string nativeSeparator;
437     nativeSeparator = sgDirPathSepBad;
438
439     while( (pos=s.find( sgDirPathSep )) != std::string::npos ) {
440         s.replace( pos, 1, nativeSeparator );
441     }
442     return s;
443 #else
444     return str();
445 #endif
446 }
447
448 bool SGPath::remove()
449 {
450     int err = ::unlink(c_str());
451     if (err) {
452         SG_LOG(SG_IO, SG_WARN,  "file remove failed: (" << str() << ") " << strerror(errno));
453     }
454     return (err == 0);
455 }
456
457 time_t SGPath::modTime() const
458 {
459     validate();
460     return _modTime;
461 }
462
463 bool SGPath::operator==(const SGPath& other) const
464 {
465     return (path == other.path);
466 }
467
468 bool SGPath::operator!=(const SGPath& other) const
469 {
470     return (path != other.path);
471 }
472
473 bool SGPath::rename(const SGPath& newName)
474 {
475     if (::rename(c_str(), newName.c_str()) != 0) {
476         SG_LOG(SG_IO, SG_WARN, "renamed failed: from " << str() << " to " << newName.str()
477             << " reason: " << strerror(errno));
478         return false;
479     }
480     
481     path = newName.path;
482     _cached = false;
483     return true;
484 }
485
486 std::string SGPath::realpath() const
487 {
488 #ifdef _WIN32
489     // Not implemented for Windows yet. Return original path instead.
490     return path;
491 #else
492     char* buf = ::realpath(path.c_str(), NULL);
493     if (!buf)
494     {
495         SG_LOG(SG_IO, SG_ALERT, "ERROR: The path '" << path << "' does not exist in the file system.");
496         return path;
497     }
498     std::string p(buf);
499     free(buf);
500     return p;
501 #endif
502 }
503