1 #include "Directories.h"
2 
3 #include "OptionsDB.h"
4 #include "i18n.h"
5 #include <GG/utf8/checked.h>
6 #include "../universe/Enums.h"
7 
8 #include <boost/filesystem/convenience.hpp>
9 #include <boost/filesystem/operations.hpp>
10 #include <boost/date_time/posix_time/posix_time.hpp>
11 
12 #include <cstdlib>
13 
14 #if defined(__FreeBSD__) || defined(__DragonFly__)
15 #include <sys/sysctl.h>
16 #endif
17 
18 namespace fs = boost::filesystem;
19 
20 namespace {
21     bool g_initialized = false;
22     fs::path bin_dir = fs::initial_path();
23 }
24 
25 #if defined(FREEORION_MACOSX)
26 
27 #include <iostream>
28 #include <sys/param.h>
29 #include <CoreFoundation/CoreFoundation.h>
30 #include <mach-o/dyld.h>
31 
32 /* sets up the directories in the following way:
33    localdir: ~/Library/FreeOrion
34    globaldir: FreeOrion.app/Contents/Resources
35    bindir:  FreeOrion.app/Contents/Executables
36    configpath: ~/Library/FreeOrion/config.xml
37    pythonhome: FreeOrion.app/Contents/Frameworks/Python.framework/Versions/{PythonMajor}.{PythonMinor}
38 */
39 namespace {
40     fs::path   s_user_dir;
41     fs::path   s_root_data_dir;
42     fs::path   s_bin_dir;
43     fs::path   s_config_path;
44     fs::path   s_python_home;
45 }
46 
47 void InitBinDir(const std::string& argv0);
48 
InitDirs(const std::string & argv0)49 void InitDirs(const std::string& argv0) {
50     if (g_initialized)
51         return;
52 
53     // store working dir
54     fs::initial_path();
55     fs::path bundle_path;
56     fs::path app_path;
57 
58     CFBundleRef bundle = CFBundleGetMainBundle();
59     char bundle_dir[MAXPATHLEN];
60 
61     if (bundle) {
62         CFURLRef bundleurl = CFBundleCopyBundleURL(bundle);
63         CFURLGetFileSystemRepresentation(bundleurl, true, reinterpret_cast<UInt8*>(bundle_dir), MAXPATHLEN);
64     } else {
65         // executable is not the main binary in application bundle (i.e. Server or AI)
66         uint32_t size = sizeof(bundle_dir);
67         if (_NSGetExecutablePath(bundle_dir, &size) != 0) {
68             std::cerr << "_NSGetExecutablePath() failed: buffer too small; need size " << size << std::endl;
69             exit(-1);
70         }
71     }
72 
73     bundle_path = fs::path(bundle_dir);
74 
75     // search bundle_path for a directory named "FreeOrion.app", exiting if not found, else constructing a path to application bundle contents
76     auto appiter = std::find(bundle_path.begin(), bundle_path.end(), "FreeOrion.app");
77     if (appiter == bundle_path.end()) {
78         std::cerr << "Error: Application bundle must be named 'FreeOrion.app' and executables must not be called from outside of it." << std::endl;
79         exit(-1);
80     } else {
81         for (auto piter = bundle_path.begin(); piter != appiter; ++piter) {
82             app_path /= *piter;
83         }
84         app_path /= "FreeOrion.app/Contents";
85     }
86 
87     s_root_data_dir =   app_path / "Resources";
88     s_user_dir      =   fs::path(getenv("HOME")) / "Library" / "Application Support" / "FreeOrion";
89     s_bin_dir       =   app_path / "Executables";
90     s_config_path   =   s_user_dir / "config.xml";
91     s_python_home   =   app_path / "Frameworks" / "Python.framework" / "Versions" / FREEORION_PYTHON_VERSION;
92 
93     fs::path p = s_user_dir;
94     if (!exists(p))
95         fs::create_directories(p);
96 
97     p /= "save";
98     if (!exists(p))
99         fs::create_directories(p);
100 
101     // Intentionally do not create the server save dir.
102     // The server save dir is publically accessible and should not be
103     // automatically created for the user.
104 
105     g_initialized = true;
106 }
107 
GetUserConfigDir()108 const fs::path GetUserConfigDir() {
109     if (!g_initialized)
110         InitDirs("");
111     return s_user_dir;
112 }
113 
GetUserDataDir()114 const fs::path GetUserDataDir() {
115     if (!g_initialized)
116         InitDirs("");
117     return s_user_dir;
118 }
119 
GetRootDataDir()120 const fs::path GetRootDataDir() {
121     if (!g_initialized)
122         InitDirs("");
123     return s_root_data_dir;
124 }
125 
GetBinDir()126 const fs::path GetBinDir() {
127     if (!g_initialized)
128         InitDirs("");
129     return s_bin_dir;
130 }
131 
GetPythonHome()132 const fs::path GetPythonHome() {
133     if (!g_initialized)
134         InitDirs("");
135     return s_python_home;
136 }
137 
138 #elif defined(FREEORION_LINUX) || defined(FREEORION_FREEBSD) || defined(FREEORION_OPENBSD) || defined(FREEORION_DRAGONFLY)
139 #include "binreloc.h"
140 #include <unistd.h>
141 #include <boost/filesystem/fstream.hpp>
142 
143 namespace {
144     /// Copy directory from to directory to only to a depth of safe_depth
copy_directory_safe(fs::path from,fs::path to,int safe_depth)145     void copy_directory_safe(fs::path from, fs::path to, int safe_depth) {
146         if (safe_depth < 0)
147             return;
148 
149         fs::copy(from, to);
150         fs::directory_iterator it(from);
151         while (it != fs::directory_iterator()) {
152             const fs::path p = *it++;
153             if (fs::is_directory(p)) {
154                 copy_directory_safe(p, to / p.filename(), safe_depth - 1);
155             } else {
156                 fs::copy(p, to / p.filename());
157             }
158         }
159     }
160 
161     /** If the old configuration directory exists, but neither
162         the XDG_CONFIG_DIR nor the XDG_DATA_DIR exist then
163         copy the config and data files and inform the user.
164 
165         It also updates the data dir in the config.xml and persisten_config.xml files.
166      */
MigrateOldConfigDirsToXDGLocation()167     void MigrateOldConfigDirsToXDGLocation() {
168         const fs::path old_path = fs::path(getenv("HOME")) / ".freeorion";
169         const fs::path config_path = GetUserConfigDir();
170         const fs::path data_path = GetUserDataDir();
171 
172         bool dont_migrate = !exists(old_path) || exists(config_path) || exists(data_path);
173         if (dont_migrate)
174             return;
175 
176         std::stringstream msg;
177         msg << "Freeorion added support for the XDG Base Directory Specification." << std::endl << std::endl
178             << "Configuration files and data were migrated from:" << std::endl
179             << old_path << std::endl << std::endl
180             << "Configuration were files copied to:" << std::endl << config_path << std::endl << std::endl
181             << "Data Files were copied to:" << std::endl << data_path << std::endl << std::endl
182             << "If your save.path option in persistent_config.xml was ~/.config, then you need to update it."
183             << std::endl;
184 
185         try {
186             fs::create_directories(config_path);
187             fs::create_directories(data_path);
188 
189             const fs::path old_config_file = old_path / "config.xml";
190             const fs::path old_persistent_file = old_path / "persistent_config.xml";
191 
192             if (exists(old_config_file))
193                 fs::copy(old_config_file, config_path / old_config_file.filename());
194             if (exists(old_persistent_file))
195                 fs::copy(old_persistent_file, config_path / old_persistent_file.filename());
196 
197             fs::directory_iterator it(old_path);
198             while (it != fs::directory_iterator()) {
199                 const fs::path p = *it++;
200                 if (p == old_config_file || p == old_persistent_file)
201                     continue;
202 
203                 if (fs::is_directory(p)) {
204                     int arbitrary_safe_depth = 6;
205                     copy_directory_safe(p, data_path / p.filename(), arbitrary_safe_depth);
206                 } else {
207                     fs::copy(p, data_path / p.filename());
208                 }
209             }
210 
211             //Start update of save.path in config file and complete it in CompleteXDGMigration()
212             fs::path sentinel = GetUserDataDir() / "MIGRATION_TO_XDG_IN_PROGRESS";
213             if (!exists(sentinel)) {
214                 fs::ofstream touchfile(sentinel);
215                 touchfile << " ";
216             }
217 
218             fs::ofstream msg_file(old_path / "MIGRATION.README");
219             msg_file << msg.str() << std::endl
220                      << "You can delete this file it is a one time message." << std::endl << std::endl;
221 
222         } catch(fs::filesystem_error const & e) {
223             std::cerr << "Error: Unable to migrate files from old config dir" << std::endl
224                       << old_path << std::endl
225                       << " to new XDG specified config dir" << std::endl << config_path << std::endl
226                       << " and data dir" << std::endl << data_path << std::endl
227                       << " because " << e.what()  << std::endl;
228             throw;
229         }
230 
231         std::cout << msg.str();
232     }
233 
234 }
235 
236 void InitBinDir(const std::string& argv0);
237 
InitDirs(const std::string & argv0)238 void InitDirs(const std::string& argv0) {
239     if (g_initialized)
240         return;
241 
242     /* store working dir.  some implimentations get the value of initial_path
243      * from the value of current_path the first time initial_path is called,
244      * so it is necessary to call initial_path as soon as possible after
245      * starting the program, so that current_path doesn't have a chance to
246      * change before initial_path is initialized. */
247     fs::initial_path();
248 
249     br_init(nullptr);
250 
251     MigrateOldConfigDirsToXDGLocation();
252 
253     fs::path cp = GetUserConfigDir();
254     if (!exists(cp)) {
255         fs::create_directories(cp);
256     }
257 
258     fs::path p = GetUserDataDir();
259     if (!exists(p)) {
260         fs::create_directories(p);
261     }
262 
263     p /= "save";
264     if (!exists(p)) {
265         fs::create_directories(p);
266     }
267 
268     // Intentionally do not create the server save dir.
269     // The server save dir is publically accessible and should not be
270     // automatically created for the user.
271 
272     InitBinDir(argv0);
273 
274     g_initialized = true;
275 }
276 
GetUserConfigDir()277 const fs::path GetUserConfigDir() {
278     static fs::path p = getenv("XDG_CONFIG_HOME")
279         ? fs::path(getenv("XDG_CONFIG_HOME")) / "freeorion"
280         : fs::path(getenv("HOME")) / ".config" / "freeorion";
281     return p;
282 }
283 
GetUserDataDir()284 const fs::path GetUserDataDir() {
285     static fs::path p = getenv("XDG_DATA_HOME")
286         ? fs::path(getenv("XDG_DATA_HOME")) / "freeorion"
287         : fs::path(getenv("HOME")) / ".local" / "share" / "freeorion";
288     return p;
289 }
290 
GetRootDataDir()291 const fs::path GetRootDataDir() {
292     if (!g_initialized) InitDirs("");
293     char* dir_name = br_find_data_dir(SHAREPATH);
294     fs::path p(dir_name);
295     std::free(dir_name);
296     p /= "freeorion";
297     // if the path does not exist, we fall back to the working directory
298     if (!exists(p)) {
299         return fs::initial_path();
300     } else {
301         return p;
302     }
303 }
304 
GetBinDir()305 const fs::path GetBinDir() {
306     if (!g_initialized) InitDirs("");
307     return bin_dir;
308 }
309 
InitBinDir(const std::string & argv0)310 void InitBinDir(const std::string& argv0) {
311     bool problem = false;
312     try {
313         // get this executable's path by following link
314         char buf[2048] = {'\0'};
315 
316 #if defined(__FreeBSD__) || defined(__DragonFly__)
317         int mib[4];
318         mib[0] = CTL_KERN;
319         mib[1] = KERN_PROC;
320         mib[2] = KERN_PROC_PATHNAME;
321         mib[3] = -1;
322         size_t buf_size = sizeof(buf);
323         sysctl(mib, 4, buf, &buf_size, 0, 0);
324 #elif defined(__OpenBSD__)
325         // OpenBSD does not have executable path's retrieval feature
326         std::string argpath(argv0);
327         boost::erase_all(argpath, "\"");
328         if (argpath[0] != '/')
329             problem = (nullptr == realpath(argpath.c_str(), buf));
330         else
331             strncpy(buf, argpath.c_str(), sizeof(buf));
332 #else
333         problem = (-1 == readlink("/proc/self/exe", buf, sizeof(buf) - 1));
334 #endif
335 
336         if (!problem) {
337             buf[sizeof(buf) - 1] = '\0';              // to be safe, else initializing an std::string with a non-null-terminated string could read invalid data outside the buffer range
338             std::string path_text(buf);
339 
340             fs::path binary_file = fs::system_complete(fs::path(path_text));
341             bin_dir = binary_file.branch_path();
342 
343             // check that a "freeoriond" file (hopefully the freeorion server binary) exists in the found directory
344             fs::path p(bin_dir);
345             p /= "freeoriond";
346             if (!exists(p))
347                 problem = true;
348         }
349 
350     } catch (...) {
351         problem = true;
352     }
353 
354     if (problem) {
355         // failed trying to parse the call path, so try hard-coded standard location...
356         char* dir_name = br_find_bin_dir(BINPATH);
357         fs::path p(dir_name);
358         std::free(dir_name);
359 
360         // if the path does not exist, fall back to the working directory
361         if (!exists(p)) {
362             bin_dir = fs::initial_path();
363         } else {
364             bin_dir = p;
365         }
366     }
367 }
368 
369 #elif defined(FREEORION_WIN32)
370 
371 void InitBinDir(const std::string& argv0);
372 
InitDirs(const std::string & argv0)373 void InitDirs(const std::string& argv0) {
374     if (g_initialized)
375         return;
376 
377     fs::path local_dir = GetUserConfigDir();
378     if (!exists(local_dir))
379         fs::create_directories(local_dir);
380 
381     fs::path p(GetSaveDir());
382     if (!exists(p))
383         fs::create_directories(p);
384 
385     // Intentionally do not create the server save dir.
386     // The server save dir is publically accessible and should not be
387     // automatically created for the user.
388 
389     InitBinDir(argv0);
390 
391     g_initialized = true;
392 }
393 
GetUserConfigDir()394 const fs::path GetUserConfigDir() {
395     static fs::path p = fs::path(std::wstring(_wgetenv(L"APPDATA"))) / "FreeOrion";
396     return p;
397 }
398 
GetUserDataDir()399 const fs::path GetUserDataDir() {
400     static fs::path p = fs::path(std::wstring(_wgetenv(L"APPDATA"))) / "FreeOrion";
401     return p;
402 }
403 
GetRootDataDir()404 const fs::path GetRootDataDir()
405 { return fs::initial_path(); }
406 
GetBinDir()407 const fs::path GetBinDir() {
408     if (!g_initialized) InitDirs("");
409     return bin_dir;
410 }
411 
GetPythonHome()412 const fs::path GetPythonHome()
413 { return GetBinDir(); }
414 
InitBinDir(const std::string & argv0)415 void InitBinDir(const std::string& argv0) {
416     try {
417         fs::path binary_file = fs::system_complete(FilenameToPath(argv0));
418         bin_dir = binary_file.branch_path();
419     } catch (const fs::filesystem_error &) {
420         bin_dir = fs::initial_path();
421     }
422 }
423 
424 #else
425 #  error Neither FREEORION_LINUX, FREEORION_FREEBSD, FREEORION_OPENBSD, FREEORION_DRAGONFLY nor FREEORION_WIN32 set
426 #endif
427 
CompleteXDGMigration()428 void CompleteXDGMigration() {
429     fs::path sentinel = GetUserDataDir() / "MIGRATION_TO_XDG_IN_PROGRESS";
430     if (exists(sentinel)) {
431         fs::remove(sentinel);
432         // Update data dir in config file
433         const std::string options_save_dir = GetOptionsDB().Get<std::string>("save.path");
434         const fs::path old_path = fs::path(getenv("HOME")) / ".freeorion";
435         if (fs::path(options_save_dir) == old_path)
436             GetOptionsDB().Set<std::string>("save.path", GetUserDataDir().string());
437     }
438 }
439 
GetResourceDir()440 const fs::path GetResourceDir() {
441     // if resource dir option has been set, use specified location. otherwise,
442     // use default location
443     std::string options_resource_dir = GetOptionsDB().Get<std::string>("resource.path");
444     fs::path dir = FilenameToPath(options_resource_dir);
445     if (fs::exists(dir) && fs::is_directory(dir))
446         return dir;
447 
448     dir = GetOptionsDB().GetDefault<std::string>("resource.path");
449     if (!fs::is_directory(dir) || !fs::exists(dir))
450         dir = FilenameToPath(GetOptionsDB().GetDefault<std::string>("resource.path"));
451 
452     return dir;
453 }
454 
GetConfigPath()455 const fs::path GetConfigPath() {
456     static const fs::path p = GetUserConfigDir() / "config.xml";
457     return p;
458 }
459 
GetPersistentConfigPath()460 const fs::path GetPersistentConfigPath() {
461     static const fs::path p = GetUserConfigDir() / "persistent_config.xml";
462     return p;
463 }
464 
GetSaveDir()465 const fs::path GetSaveDir() {
466     // if save dir option has been set, use specified location.  otherwise,
467     // use default location
468     std::string options_save_dir = GetOptionsDB().Get<std::string>("save.path");
469     if (options_save_dir.empty())
470         options_save_dir = GetOptionsDB().GetDefault<std::string>("save.path");
471     return FilenameToPath(options_save_dir);
472 }
473 
GetServerSaveDir()474 const fs::path GetServerSaveDir() {
475     // if server save dir option has been set, use specified location.  otherwise,
476     // use default location
477     std::string options_save_dir = GetOptionsDB().Get<std::string>("save.server.path");
478     if (options_save_dir.empty())
479         options_save_dir = GetOptionsDB().GetDefault<std::string>("save.server.path");
480     return FilenameToPath(options_save_dir);
481 }
482 
RelativePath(const fs::path & from,const fs::path & to)483 fs::path RelativePath(const fs::path& from, const fs::path& to) {
484     fs::path retval;
485     fs::path from_abs = fs::absolute(from);
486     fs::path to_abs = fs::absolute(to);
487     auto from_it = from_abs.begin();
488     auto end_from_it = from_abs.end();
489     auto to_it = to_abs.begin();
490     auto end_to_it = to_abs.end();
491     while (from_it != end_from_it && to_it != end_to_it && *from_it == *to_it) {
492         ++from_it;
493         ++to_it;
494     }
495     for (; from_it != end_from_it; ++from_it)
496     { retval /= ".."; }
497     for (; to_it != end_to_it; ++to_it)
498     { retval /= *to_it; }
499     return retval;
500 }
501 
502 #if defined(FREEORION_WIN32)
503 
PathToString(const fs::path & path)504 std::string PathToString(const fs::path& path) {
505     fs::path::string_type native_string = path.generic_wstring();
506     std::string retval;
507     utf8::utf16to8(native_string.begin(), native_string.end(), std::back_inserter(retval));
508     return retval;
509 }
510 
FilenameToPath(const std::string & path_str)511 fs::path FilenameToPath(const std::string& path_str) {
512     // convert UTF-8 directory string to UTF-16
513     boost::filesystem::path::string_type directory_native;
514     utf8::utf8to16(path_str.begin(), path_str.end(), std::back_inserter(directory_native));
515 #if (BOOST_VERSION >= 106300)
516     return fs::path(directory_native).generic_path();
517 #else
518     return fs::path(directory_native);
519 #endif
520 }
521 
522 #else // defined(FREEORION_WIN32)
523 
PathToString(const fs::path & path)524 std::string PathToString(const fs::path& path)
525 { return path.string(); }
526 
FilenameToPath(const std::string & path_str)527 fs::path FilenameToPath(const std::string& path_str)
528 { return fs::path(path_str); }
529 
530 #endif // defined(FREEORION_WIN32)
531 
FilenameTimestamp()532 std::string FilenameTimestamp() {
533     boost::posix_time::time_facet* facet = new boost::posix_time::time_facet("%Y%m%d_%H%M%S");
534     std::stringstream date_stream;
535 
536     date_stream.imbue(std::locale(date_stream.getloc(), facet));// alternate locales: GetLocale("en_US.UTF-8") or GetLocale("ja_JA.UTF-8") or date_stream.getloc()
537     date_stream << boost::posix_time::microsec_clock::local_time();
538     std::string retval = date_stream.str();
539     TraceLogger() << "Filename initial timestamp: " << retval << " is valid utf8?: " << utf8::is_valid(retval.begin(), retval.end());
540 
541     // replace spaces and colons with safer chars for filenames
542     std::replace(retval.begin(), retval.end(), ' ', '_');
543     std::replace(retval.begin(), retval.end(), ':', '-');
544 
545     // filter non-filename-safe characters that are valid single-byte UTF-8 characters, in case the default locale for this system has weird chars in the time-date format
546     auto do_remove = [](char c) -> bool { return !std::isalnum(c) && c <= 127 && c != '_' && c != '-'; };
547     retval.erase(std::remove_if(retval.begin(), retval.end(), do_remove), retval.end());
548     TraceLogger() << "Filename filtered timestamp: " << retval << " is valid utf8?: " << utf8::is_valid(retval.begin(), retval.end());
549 
550     return retval;
551 }
552 
IsFOCScript(const boost::filesystem::path & path)553 bool IsFOCScript(const boost::filesystem::path& path)
554 { return fs::is_regular_file(path) && ".txt" == path.extension() && path.stem().extension() == ".focs"; }
555 
ListDir(const fs::path & path,std::function<bool (const fs::path &)> predicate)556 std::vector<fs::path> ListDir(const fs::path& path, std::function<bool (const fs::path&)> predicate) {
557     std::vector<fs::path> retval;
558 
559     if (!predicate)
560         predicate = static_cast<bool (*)(const fs::path&)>(fs::is_regular_file);
561 
562     bool is_rel = path.is_relative();
563     if (!is_rel && (fs::is_empty(path) || !fs::is_directory(path))) {
564         DebugLogger() << "ListDir: File " << PathToString(path) << " was not included as it is empty or not a directoy";
565     } else {
566         const fs::path& default_path = is_rel ? GetResourceDir() / path : path;
567 
568         for (fs::recursive_directory_iterator dir_it(default_path);
569              dir_it != fs::recursive_directory_iterator(); ++dir_it)
570         {
571             if (predicate(dir_it->path()))
572                 retval.push_back(dir_it->path());
573             else
574                 TraceLogger() << "ListDir: Discarding non-matching path: " << PathToString(dir_it->path());
575         }
576     }
577 
578     if (retval.empty()) {
579         DebugLogger() << "ListDir: No paths found for " << path.string();
580     }
581 
582     return retval;
583 }
584 
IsValidUTF8(const std::string & in)585 bool IsValidUTF8(const std::string& in)
586 { return utf8::is_valid(in.begin(), in.end()); }
587 
IsInDir(const fs::path & dir,const fs::path & test_dir)588 bool IsInDir(const fs::path& dir, const fs::path& test_dir) {
589     if (!fs::exists(dir) || !fs::is_directory(dir))
590         return false;
591 
592     if (fs::exists(test_dir) && !fs::is_directory(test_dir))
593         return false;
594 
595     // Resolve any symbolic links, dots or dot-dots
596     auto canon_dir = fs::canonical(dir);
597     // Don't resolve path if directory doesn't exist
598     // TODO: Change to fs::weakly_canonical after bump boost version above 1.60
599     auto canon_path = test_dir;
600     if (fs::exists(test_dir))
601         canon_path = fs::canonical(test_dir);
602 
603     // Paths shorter than dir are not in dir
604     auto dir_length = std::distance(canon_dir.begin(), canon_dir.end());
605     auto path_length = std::distance(canon_path.begin(), canon_path.end());
606     if (path_length < dir_length)
607         return false;
608 
609     // Check that the whole dir path matches the test path
610     // Extra portions of path are contained in dir
611     return std::equal(canon_dir.begin(), canon_dir.end(), canon_path.begin());
612 }
613 
GetPath(PathType path_type)614 fs::path GetPath(PathType path_type) {
615     switch (path_type) {
616     case PATH_BINARY:
617         return GetBinDir();
618     case PATH_RESOURCE:
619         return GetResourceDir();
620     case PATH_DATA_ROOT:
621         return GetRootDataDir();
622     case PATH_DATA_USER:
623         return GetUserDataDir();
624     case PATH_CONFIG:
625         return GetUserConfigDir();
626     case PATH_SAVE:
627         return GetSaveDir();
628     case PATH_TEMP:
629         return fs::temp_directory_path();
630     case PATH_PYTHON:
631 #if defined(FREEORION_MACOSX) || defined(FREEORION_WIN32)
632         return GetPythonHome();
633 #endif
634     case PATH_INVALID:
635     default:
636         ErrorLogger() << "Invalid path type " << path_type;
637         return fs::temp_directory_path();
638     }
639 }
640 
GetPath(const std::string & path_string)641 fs::path GetPath(const std::string& path_string) {
642     if (path_string.empty()) {
643         ErrorLogger() << "GetPath called with empty argument";
644         return fs::temp_directory_path();
645     }
646 
647     PathType path_type;
648     try {
649         path_type = boost::lexical_cast<PathType>(path_string);
650     } catch (const boost::bad_lexical_cast& ec) {
651         // try partial match
652         std::string retval = path_string;
653         for (const auto& path_type_str : PathTypeStrings()) {
654             std::string path_type_string = PathToString(GetPath(path_type_str));
655             boost::replace_all(retval, path_type_str, path_type_string);
656         }
657         if (path_string != retval) {
658             return FilenameToPath(retval);
659         } else {
660             ErrorLogger() << "Invalid cast for PathType from string " << path_string;
661             return fs::temp_directory_path();
662         }
663     }
664     return GetPath(path_type);
665 }
666 
IsExistingFile(const fs::path & path)667 bool IsExistingFile(const fs::path& path) {
668     try {
669         auto stat = fs::status(path);
670         return fs::exists(stat) && fs::is_regular_file(stat);
671     } catch(boost::filesystem::filesystem_error& ec) {
672         ErrorLogger() << "Filesystem error during stat of " << PathToString(path) << " : " << ec.what();
673     }
674 
675     return false;
676 }
677