1 /** @file gsFileManager.cpp
2
3 @brief Utility class for finding files and handling paths
4
5 This file is part of the G+Smo library.
6
7 This Source Code Form is subject to the terms of the Mozilla Public
8 License, v. 2.0. If a copy of the MPL was not distributed with this
9 file, You can obtain one at http://mozilla.org/MPL/2.0/.
10
11 Author(s): S. Takacs, A. Mantzaflaris, H. Weiner, J. Vogl
12 */
13
14 #include <gsIO/gsFileManager.h>
15 #include <iostream>
16 #include <fstream>
17 #include <gsCore/gsConfig.h>
18 #include <gsUtils/gsUtils.h>
19 #include <cstdlib>
20 #include <sys/stat.h>
21
22 #if defined _WIN32
23 #include <windows.h>
24 #include <direct.h>
25 #include <ShlObj.h>
26 #else
27 #include <dlfcn.h>
28 #include <unistd.h>
29 #include <pwd.h>
30 #include <dirent.h>
31 #endif
32
33 #if defined __APPLE__
34 #include <mach-o/dyld.h>
35 #include <cstring>
36 #include <sys/syslimits.h>
37 #endif
38
39 #if defined __linux__
40 #include <limits.h>
41 #endif
42
43 #if defined __FreeBSD__
44 #include <sys/syslimits.h>
45 #endif
46
47 namespace gismo
48 {
49
50 // Struct for storing data
51 struct gsFileManagerData {
52 gsFileManagerData();
53 std::vector<std::string> m_paths;
54 };
55
gsFileManagerDataSingleton()56 gsFileManagerData& gsFileManagerDataSingleton()
57 {
58 static gsFileManagerData singleton;
59 return singleton;
60 }
61
fileExists(const std::string & name)62 bool gsFileManager::fileExists(const std::string& name)
63 {
64 return !find(name).empty();
65 }
66
fileExistsInDataDir(const std::string & name)67 bool gsFileManager::fileExistsInDataDir(const std::string& name)
68 {
69 return !findInDataDir(name).empty();
70 }
71
72 namespace {
_dirExistsWithoutSearching(const std::string & path)73 bool _dirExistsWithoutSearching(const std::string& path)
74 {
75 struct stat info;
76 return (0==stat(path.c_str(), &info)) && (info.st_mode & S_IFDIR);
77 }
78
_fileExistsWithoutSearching(const std::string & fn)79 bool _fileExistsWithoutSearching(const std::string& fn)
80 {
81 // Note:
82 // std::ifstream s(fn.c_str()); return s.good() && ! s.eof();
83 // is also possible; however that treats empty files as non-existing.
84 #if defined _WIN32
85 // doesn' t work for relative paths, so make absolute.
86 std::string fnc = fn;
87 if (gsFileManager::isExplicitlyRelative(fnc))
88 fnc = gsFileManager::getCanonicRepresentation(gsFileManager::getCurrentPath() + fnc);
89 DWORD dwAttrib = GetFileAttributes( fnc.c_str() );
90 return (dwAttrib != INVALID_FILE_ATTRIBUTES && !(dwAttrib & FILE_ATTRIBUTE_DIRECTORY));
91 #else
92 struct stat buf;
93 return ( (0==stat(fn.c_str(), &buf)) && (0!=S_ISREG(buf.st_mode)) );
94 #endif
95 }
96 } // anonymous namespace
97
getValidPathSeparators()98 const std::string& gsFileManager::getValidPathSeparators()
99 {
100 #if defined _WIN32 || defined __CYGWIN__
101 static const std::string ps("\\/");
102 #else
103 static const std::string ps("/");
104 #endif
105 return ps;
106 }
107
getNativePathSeparator()108 char gsFileManager::getNativePathSeparator()
109 {
110 return getValidPathSeparators()[0];
111 }
112
getInvalidCharacters()113 const std::string& gsFileManager::getInvalidCharacters()
114 {
115 #if defined _WIN32 || defined __CYGWIN__
116 static std::string ps(":*?\"<>|"); // ':' allowed at fn[1]
117 #else
118 // allowed under Unix: \?%*:|"<>
119 static std::string ps("");
120 #endif
121 return ps;
122 }
123
124 namespace {
_contains(const std::string & haystack,char needle)125 inline bool _contains( const std::string& haystack, char needle )
126 {
127 return haystack.find(needle) != std::string::npos;
128 }
129
_isValidPathOrName(const std::string & fn)130 bool _isValidPathOrName(const std::string& fn)
131 {
132 bool invalid = false;
133 for (size_t i = 0; i < fn.length(); ++i)
134 {
135 invalid = invalid | _contains(gsFileManager::getInvalidCharacters(), fn[i]);
136 }
137 return !invalid;
138 }
139
140 // sets last character to the native path seperator
141 // special case "" gets "./"
_makePath(std::string & final_result)142 void _makePath(std::string& final_result)
143 {
144 if (final_result.length() == 0)
145 final_result.push_back('.');
146 if (final_result[final_result.length() - 1] != gsFileManager::getNativePathSeparator())
147 final_result.push_back(gsFileManager::getNativePathSeparator());
148 }
149
150 } // anonymous namespace
151
isFullyQualified(const std::string & fn)152 bool gsFileManager::isFullyQualified(const std::string& fn)
153 {
154 bool valid = false;
155 #if defined _WIN32 || defined __CYGWIN__
156 // case "c:\\abc"
157 if ( fn.size() > 2 && isalpha(fn[0]) && fn[1] == ':')
158 {
159 for (size_t i = 0; i < getValidPathSeparators().length(); ++i)
160 {
161 valid = valid || (fn[2] == getValidPathSeparators()[i]);
162 }
163 valid = valid && _isValidPathOrName(fn.substr(3, fn.length()-3));
164 }
165 else // case "\\abc", same as Unix
166 {
167 #endif
168 for (size_t i = 0; i < getValidPathSeparators().length(); ++i)
169 {
170 valid = valid || (fn[0] == getValidPathSeparators()[i]);
171 }
172 valid = valid && _isValidPathOrName(fn);
173 #if defined _WIN32 || defined __CYGWIN__
174 }
175 #endif
176 return valid;
177 }
178
isExplicitlyRelative(const std::string & fn)179 bool gsFileManager::isExplicitlyRelative(const std::string& fn)
180 {
181 bool valid = false;
182 for (size_t i = 0; i < getValidPathSeparators().length(); ++i)
183 {
184 valid = valid || (fn[0] == '.' && fn[1] == getValidPathSeparators()[i]) ||
185 (fn[0] == '.' && fn[1] == '.' && fn[2] == getValidPathSeparators()[i]);
186 }
187 valid = valid && _isValidPathOrName(fn);
188 return valid;
189 }
190
191 namespace {
192
_replace_with_native_separator(std::string & str)193 inline void _replace_with_native_separator(std::string & str)
194 {
195 for (size_t i = 1; i < gsFileManager::getValidPathSeparators().length(); ++i)
196 {
197 std::replace(str.begin(), str.end(),
198 gsFileManager::getValidPathSeparators()[i],
199 gsFileManager::getNativePathSeparator());
200 }
201 }
202
_addSearchPaths(const std::string & in,std::vector<std::string> & out)203 inline bool _addSearchPaths(const std::string& in, std::vector<std::string>& out)
204 {
205 bool ok = true;
206 std::string p;
207 std::string::const_iterator a;
208 std::string::const_iterator b = in.begin();
209 while (true)
210 {
211 a = b;
212 while (b != in.end() && (*b) != ';') { ++b; }
213
214 p.assign(a,b);
215
216 #if defined _WIN32
217 _replace_with_native_separator(p);
218 #endif
219
220 if (!p.empty())
221 {
222 #if defined _WIN32
223 if (*p.rbegin() != '\\')
224 p.push_back('\\');
225 #else
226 if (*p.rbegin() != '/')
227 p.push_back('/');
228 #endif
229
230 #if defined(_MSC_VER) && _MSC_VER < 1900
231 // with VS2013, a path must not end with pathseperator
232 if(_dirExistsWithoutSearching(gsFileManager::getCanonicRepresentation(p + "..")))
233 #else
234 if (_dirExistsWithoutSearching(p))
235 #endif // defined(_MSC_VER) && _MSC_VER < 1900
236 out.push_back(p);
237 else
238 ok = false;
239 }
240
241 if ( b == in.end() ) break;
242
243 ++b;
244 }
245 return ok;
246 }
247
248 } // anonymous namespace
249
250 // constructor; registers data from macro
gsFileManagerData()251 gsFileManagerData::gsFileManagerData()
252 {
253 #ifdef GISMO_SEARCH_PATHS
254 (void)_addSearchPaths("" GISMO_SEARCH_PATHS, m_paths);
255 #endif
256 }
257
addSearchPaths(const std::string & paths)258 bool gsFileManager::addSearchPaths(const std::string& paths)
259 {
260 return _addSearchPaths( paths, gsFileManagerDataSingleton().m_paths );
261 }
262
setSearchPaths(const std::string & paths)263 bool gsFileManager::setSearchPaths(const std::string& paths)
264 {
265 gsFileManagerDataSingleton().m_paths.clear();
266 return _addSearchPaths( paths, gsFileManagerDataSingleton().m_paths );
267 }
268
getSearchPaths()269 std::string gsFileManager::getSearchPaths()
270 {
271 std::string result;
272 gsFileManagerData& dat = gsFileManagerDataSingleton();
273 for (std::vector<std::string>::const_iterator it = dat.m_paths.begin();
274 it < dat.m_paths.end(); ++it)
275 {
276 result += (*it) + ";";
277 }
278 return result;
279 }
280
find(std::string fn)281 std::string gsFileManager::find(std::string fn)
282 {
283 #if defined _WIN32
284 _replace_with_native_separator(fn);
285 #endif
286
287 if ( _fileExistsWithoutSearching(fn) ) return fn;
288
289 if ( isFullyQualified(fn) || isExplicitlyRelative(fn) ) return std::string();
290
291 gsFileManagerData& dat = gsFileManagerDataSingleton();
292
293 std::string tmp;
294 for (std::vector<std::string>::const_iterator it = dat.m_paths.begin();
295 it < dat.m_paths.end(); ++it)
296 {
297 tmp = (*it) + fn;
298 if ( _fileExistsWithoutSearching(tmp) )
299 {
300 if(isExplicitlyRelative(tmp))
301 tmp = getCanonicRepresentation(getCurrentPath() + tmp);
302 return tmp;
303 }
304 }
305
306 return std::string();
307 }
308
findInDataDir(std::string fn)309 std::string gsFileManager::findInDataDir(std::string fn)
310 {
311 #if defined _WIN32
312 _replace_with_native_separator(fn);
313 #endif
314
315 // We know that GISMO_DATA_DIR ends with a path separator, but
316 // maybe the user does not know it.
317 if ( fn[0] == '/' || fn[0] == '\\' ) fn.erase(0,1);
318
319 std::string fn_out = GISMO_DATA_DIR + fn;
320
321 if ( _fileExistsWithoutSearching(fn_out) ) return fn_out;
322
323 return std::string();
324 }
325
mkdir(std::string fn)326 bool gsFileManager::mkdir( std::string fn )
327 {
328 if (!isFullyQualified(fn))
329 {
330 fn = getCanonicRepresentation(getCurrentPath() + fn);
331 if (fn == "")
332 return false;
333 }
334 #if defined _WIN32
335 _replace_with_native_separator(fn);
336 return !fileExists(fn) && (0 != CreateDirectoryA(fn.c_str(), NULL) || (GetLastError() == ERROR_ALREADY_EXISTS));
337 #else
338 // create, if successful, return true;
339 if (0 == ::mkdir(fn.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH))
340 return true;
341 // create was unsuccessful, does the directory already exists?
342 bool exists = false;
343 DIR* dir = opendir(fn.c_str());
344 if(dir)
345 {
346 exists = true;
347 closedir(dir);
348 }
349 return exists;
350 #endif
351 }
352
getTempPath()353 std::string gsFileManager::getTempPath()
354 {
355 #if defined _WIN32
356 TCHAR _temp[MAX_PATH];
357 DWORD l = GetTempPath(/*length of buffer:*/MAX_PATH, _temp);
358 GISMO_UNUSED(l);
359 GISMO_ASSERT(l, "GetTempPath did return 0");
360 return std::string(_temp);
361 #else
362
363 // Typically, we should consider TMPDIR
364 // http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_03
365 // https://en.wikipedia.org/wiki/TMPDIR&oldid=728654758
366 char * _temp = getenv ("TMPDIR");
367 // getenv returns NULL ptr if the variable is unknown (http://en.cppreference.com/w/cpp/utility/program/getenv).
368 // If it is an empty string, we should also exclude it.
369 if (_temp != NULL && _temp[0] != '\0')
370 {
371 // note: env variable needs no free
372 std::string path(_temp);
373 _makePath(path);
374 return path;
375 }
376
377 // Okey, if first choice did not work, try this:
378 _temp = getenv("TEMP");
379 if (_temp != NULL && _temp[0] != '\0')
380 {
381 // note: env variable needs no free
382 std::string path(_temp);
383 _makePath(path);
384 return path;
385 }
386
387 // And as third choice, use just current directory
388 // http://man7.org/linux/man-pages/man2/getcwd.2.html
389 _temp = getcwd(NULL, 0);
390 GISMO_ASSERT(NULL!=_temp, "getcwd returned NULL.");
391 std::string path(_temp);
392 // The string is allocated using malloc, see the reference above
393 std::free(_temp);
394 _makePath(path);
395 return path;
396 #endif
397 }
398
getCurrentPath()399 std::string gsFileManager::getCurrentPath()
400 {
401 #if defined _WIN32
402 TCHAR _temp[MAX_PATH];
403 DWORD l = GetCurrentDirectory(MAX_PATH, _temp);
404 GISMO_UNUSED(l);
405 GISMO_ASSERT(l, "GetCurrentDirectory did return 0");
406 std::string path(_temp);
407 _makePath(path);
408 return path;
409 #else
410 // http://man7.org/linux/man-pages/man2/getcwd.2.html
411 char* _temp = getcwd(NULL, 0);
412 GISMO_ASSERT(NULL!=_temp, "getcwd returned NULL.");
413 std::string path(_temp);
414 // The string is allocated using malloc, see the reference above
415 std::free(_temp);
416 _makePath(path);
417 return path;
418 #endif
419 }
420
getExePath()421 std::string gsFileManager::getExePath()
422 {
423 #if defined _WIN32
424 TCHAR _temp[MAX_PATH];
425 DWORD l = GetModuleFileName( NULL, _temp, MAX_PATH );
426 GISMO_UNUSED(l);
427 GISMO_ASSERT(l, "GetModuleFileName did return 0");
428 GISMO_ASSERT(_fileExistsWithoutSearching(_temp),
429 "The executable cannot be found where it is expected." );
430 return getPath(std::string(_temp));
431 #elif defined __linux__
432 char exePath[PATH_MAX];
433 ssize_t len = ::readlink("/proc/self/exe", exePath, sizeof(exePath));
434 if (len == -1 || len == sizeof(exePath))
435 len = 0;
436 exePath[len] = '\0';
437 return getPath(std::string(exePath));
438 #elif defined __FreeBSD__ //https://stackoverflow.com/questions/1023306/finding-current-executables-path-without-proc-self-exe
439 char exePath[PATH_MAX];
440 ssize_t len = ::readlink("/proc/self/exe", exePath, sizeof(exePath));
441 if (len == -1 || len == sizeof(exePath))
442 len = 0;
443 exePath[len] = '\0';
444 return getPath(std::string(exePath));
445 #elif defined __APPLE__
446 char exePath[PATH_MAX];
447 uint32_t len = sizeof(exePath);
448 if (_NSGetExecutablePath(exePath, &len) != 0)
449 {
450 exePath[0] = '\0'; // buffer too small (!)
451 } else
452 {
453 // resolve symlinks, ., .. if possible
454 char *canonicalPath = realpath(exePath, NULL);
455 if (canonicalPath != NULL)
456 {
457 strncpy(exePath,canonicalPath,len);
458 free(canonicalPath);
459 }
460 }
461 return getPath(std::string(exePath));
462 #endif
463 }
464
getHomePath()465 std::string gsFileManager::getHomePath()
466 {
467 #if defined _WIN32
468 PWSTR wbuffer[MAX_PATH];
469 char _temp[MAX_PATH];
470 if (SHGetKnownFolderPath(FOLDERID_Profile, KF_FLAG_DEFAULT, NULL, wbuffer) == S_OK)
471 {
472 wcsrtombs_s(NULL, _temp,
473 const_cast<const wchar_t**>(reinterpret_cast<wchar_t**>(wbuffer)),
474 MAX_PATH, NULL);
475 }
476 #else
477 char* _temp = getenv("HOME");
478 if (NULL == _temp || _temp[0] == '\0')
479 {
480 _temp = getpwuid(getuid())->pw_dir;
481 }
482 #endif
483 GISMO_ASSERT((NULL != _temp) && (_temp[0] != '\0'), "Can't get Home directory.");
484 std::string path(_temp);
485 _makePath(path);
486 return path;
487 }
488
makeRelative(const std::string & from,const std::string & to)489 std::string gsFileManager::makeRelative(const std::string & from, const std::string & to)
490 {
491 if (!isFullyQualified(from))
492 return "";
493
494 std::string fromc = getCanonicRepresentation(from);
495 std::string toc = getCanonicRepresentation((isFullyQualified(to) ?
496 "" : getCurrentPath()) + to);
497
498 #if defined _WIN32
499 // if both start with diverent driveletter, return toc
500 if ((fromc.length() > 0) && isalpha(fromc[0]) && (toc.length() > 0) && isalpha(toc[0]) &&
501 (fromc[0] != toc[0]))
502 return toc;
503 #endif // defined _WIN32
504
505 size_t start = 0;
506 size_t pos = 0;
507 while (pos < fromc.length() && pos < toc.length() && fromc[pos] == toc[pos])
508 {
509 if (fromc[pos++] == getNativePathSeparator())
510 start = pos;
511 }
512
513 std::string result;
514 std::string sub = fromc.substr(start);
515 size_t deep = std::count(sub.begin(), sub.end(), getNativePathSeparator());
516 if (deep == 0)
517 {
518 result += ".";
519 result.push_back(getNativePathSeparator());
520 }
521 else
522 for (size_t i = 0; i < deep; ++i)
523 {
524 result += "..";
525 result.push_back(getNativePathSeparator());
526 }
527 result += toc.substr(start);
528
529 return result;
530 }
531
pathEqual(const std::string & p1o,const std::string & p2o)532 bool gsFileManager::pathEqual( const std::string& p1o, const std::string& p2o )
533 {
534 std::string p1 = p1o;
535 std::string p2 = p2o;
536
537 if (!isFullyQualified(p1))
538 p1 = getCurrentPath() + p1;
539 GISMO_ASSERT(isFullyQualified(p1), "p1 isn't a absolute path");
540 if (!isFullyQualified(p2))
541 p2 = getCurrentPath() + p2;
542 GISMO_ASSERT(isFullyQualified(p1), "p2 isn't a absolute path");
543
544 // canonic representation as path
545 p1 = getCanonicRepresentation(p1, true);
546 p2 = getCanonicRepresentation(p2, true);
547
548 const size_t sz = p1.size();
549
550 if (sz != p2.size())
551 return false;
552
553 for (size_t i = 0; i < sz; ++i)
554 {
555 if (!(
556 p1[i] == p2[i]
557 || (p1[i] == '/' && p2[i] == '\\')
558 || (p1[i] == '\\' && p2[i] == '/')
559 ))
560 return false;
561
562 }
563 return true;
564 }
565
getExtension(std::string const & fn)566 std::string gsFileManager::getExtension(std::string const & fn)
567 {
568 if(fn.find_last_of(".") != std::string::npos)
569 {
570 std::string ext = fn.substr(fn.rfind(".")+1);
571 std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
572 return ext;
573 }
574 return "";
575 }
576
getBasename(std::string const & fn)577 std::string gsFileManager::getBasename(std::string const & fn)
578 {
579 if(fn.find_last_of(".") != std::string::npos)
580 {
581 size_t pos1 = fn.find_last_of(getValidPathSeparators());
582 size_t pos2 = fn.rfind(".");
583 std::string name = fn.substr(pos1+1, pos2-pos1-1);
584 return name;
585 }
586 return fn;
587 }
588
getFilename(std::string const & fn)589 std::string gsFileManager::getFilename(std::string const & fn)
590 {
591 size_t pos1 = fn.find_last_of(getValidPathSeparators());
592 if(pos1 != std::string::npos)
593 {
594 std::string name = fn.substr(pos1+1);
595 return name;
596 }
597 return fn;
598 }
599
getPath(std::string const & fn,bool resolve)600 std::string gsFileManager::getPath(std::string const & fn, bool resolve)
601 {
602 if (resolve)
603 return getCanonicRepresentation(gsFileManager::find(fn) + "/../");
604 return getCanonicRepresentation(fn + "/../");
605 }
606
607 namespace {
608 struct gsStringView {
609 const char* m_begin;
610 const char* m_end;
611
gsStringViewgismo::__anonf36afa9c0411::gsStringView612 gsStringView( const std::string& s, size_t b, size_t e )
613 : m_begin(s.c_str()+b), m_end(s.c_str()+e) {}
614
operator ==gismo::__anonf36afa9c0411::gsStringView615 bool operator==(const char* c) const
616 {
617 for (const char* it=m_begin; it<m_end; ++it, ++c)
618 if ( *it != *c ) return false;
619 return *c == '\0';
620 }
621
begingismo::__anonf36afa9c0411::gsStringView622 const char* begin() const { return m_begin; }
endgismo::__anonf36afa9c0411::gsStringView623 const char* end() const { return m_end; }
624
625 };
626 } // end anonymous namespace
627
getCanonicRepresentation(const std::string & s,bool asPath)628 std::string gsFileManager::getCanonicRepresentation(const std::string& s, bool asPath)
629 {
630 std::vector<gsStringView> parts;
631 size_t last = 0;
632 for (size_t i=0; i<s.size(); ++i)
633 if (_contains(getValidPathSeparators(),s[i]))
634 {
635 parts.push_back(gsStringView(s,last,i));
636 last = i + 1;
637 }
638 parts.push_back(gsStringView(s,last,s.size()));
639
640 std::vector<gsStringView> result;
641 size_t sz = parts.size();
642 for (size_t i=0; i<sz; ++i)
643 {
644 if (parts[i] == "" && i > 0 && i < sz-1)
645 ; // discard part
646 else if (parts[i] == "." && i > 0)
647 ; // discard
648 else if (parts[i] == "..")
649 {
650 if (result.empty() || result.back() == "..")
651 result.push_back(parts[i]);
652 else if (result.back() == "")
653 gsWarn << "Cannot go above root.\n";
654 else if (result.back() == ".")
655 {
656 result.pop_back();
657 result.push_back(parts[i]);
658 }
659 else
660 result.pop_back();
661 }
662 else
663 result.push_back(parts[i]);
664 }
665
666 std::string final_result;
667 final_result.append( result[0].begin(), result[0].end() );
668 for (size_t i=1; i<result.size(); ++i)
669 {
670 final_result.push_back( getNativePathSeparator() );
671 final_result.append( result[i].begin(), result[i].end() );
672 }
673 if (asPath) {
674 _makePath(final_result);
675 }
676 return final_result;
677 }
678
679 // todo: return bool for success/failure
open(const std::string & fn)680 void gsFileManager::open(const std::string & fn)
681 {
682
683 #if defined __APPLE__
684 const int ret = std::system( ("open " + fn + " &").c_str() );
685 #elif defined __unix__ //__linux__
686 const int ret = std::system( ("xdg-open " + fn + " &").c_str() );
687 #elif defined _WIN32
688 HINSTANCE hi = ShellExecute(GetDesktopWindow(), "open", fn.c_str(),
689 NULL, NULL, SW_SHOWNORMAL);
690 const bool ret = !( (INT_PTR)hi>32);
691 #else
692 GISMO_STATIC_ASSERT(0,"Platform not identified");
693 #endif
694 //return ret;
695 if (0!=ret)
696 gsWarn<<"\nFailed to open file "<<fn<<
697 " using OS preferred application.\n\n";
698 }
699
700 } //namespace gismo
701