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